LMS - edmozley/freeitsm GitHub Wiki

LMS (Learning Management System)

Folder: lms/ Colour: Blue #2563eb

A learning platform built into the service desk. Two kinds of course live side by side:

  • Authored courses โ€” written in FreeITSM: lessons in a rich-text editor, quiz questions, a pass mark you set. No external tool needed. See LMS Authoring and the AI Authoring deep dive.
  • SCORM courses โ€” uploaded packages (SCORM 1.1 / 1.2 / 2004) authored in Articulate, Rise, iSpring and the like, played in a sandboxed runtime.

Both are assigned to people, taken, and tracked through the same progress model โ€” so an admin's Progress tab and a learner's My Courses page work identically whichever kind a course is.

A learner is not necessarily an analyst. A course can go to a group of analysts, to a group holding analysts and self-service portal users together, to one named person, or to everyone on the portal โ€” and a portal user takes it in the portal, with the same player and the same marking. See Training for Portal Users.

A course's kind is a column, lms_courses.content_type ('native' | 'scorm'), and player.php dispatches on it. Everything downstream (groups, assignments, progress, the learner drill-down) is shared.


Who can do what โ€” learner vs manager

The LMS is the first module split by the Roles system. There are two distinct things a person can do:

Role Needs Can
Learner the lms module (Layer 1 access) Take the courses assigned to them, and only those. Lands on My Courses.
Manager the lms.manage capability (a Role, or is_admin) Author, upload, run groups, assign training, and see everyone's progress. Lands on the admin dashboard.

My Courses (lms/my-courses.php) lists only the courses assigned to the learner (via their groups), each with its own status and a Start / Resume / Review button.

This is enforced server-side, not just hidden: the management pages and APIs sit behind lms.manage; playback and the learner content APIs are gated to assigned-to-me-or-manager. A learner can neither reach the admin screens nor open โ€” or pull the content and answer key of โ€” a course that wasn't assigned to them, by URL or by direct API call. Managers and admins may open any course, which is how Preview works. See LMS Authoring โ†’ The learner/manager split.


Authored courses (content_type = 'native')

A course is ordered lessons, each carrying optional questions:

  • Lessons are TinyMCE HTML, exactly as a knowledge article is โ€” including inline images, so a lesson is self-contained. Drag to reorder.
  • Questions are single-choice, multiple-choice or true/false, each with an explanation shown to the learner afterwards.
  • The pass mark belongs to FreeITSM (lms_courses.pass_mark), not to the package. Score is the percentage of questions correct across the course; the learner is marked passed / failed against the mark, or simply completed if there is no mark.
  • Grading is server-side and authoritative. The learner's content feed never sends is_correct, so the answer key never reaches the browser โ€” closing the hole that makes a SCORM quiz trivially cheatable.

Full detail: LMS Authoring. The three AI helpers (article โ†’ lesson, topic โ†’ outline, lesson โ†’ quiz): LMS AI Authoring.

The database is the source of truth for authored content, so fixing a typo is instant and nobody's progress resets. "Export as SCORM" is a possible future feature (generate a package on demand for portability), deliberately not the runtime.

SCORM courses (content_type = 'scorm')

  • Upload a SCORM ZIP โ€” the imsmanifest.xml is parsed for the SCORM version and launch URL.
  • Files extract to lms/content/<course-id>/. Uploads are vetted before extraction โ€” an allowlist of course file types (never .php), zip-slip rejection, and size/ratio caps against zip bombs, plus an .htaccess that disables code execution for the whole tree. See LMS Authoring โ†’ SCORM upload safety.
  • Player (player.php): a sandboxed full-viewport iframe with a dual JavaScript API bridge โ€” both window.API (SCORM 1.x) and window.API_1484_11 (2004) โ€” so a package finds whichever it looks for. CMI calls bridge to the backend; cmi.suspend_data / cmi.location persist for resume.

Groups & assignments

  • Learning groups are groups of analysts ("Service Desk L1", "Engineers"), made on the Groups tab here. Membership is many-to-many.
  • People groups hold analysts and portal users together and are made under Tickets โ†’ Users โ†’ Groups. They are shared with the rest of FreeITSM โ€” the same group can be given a knowledge folder. See People Groups.
  • Assign a course with an optional deadline to a learning group, a people group, everyone on the portal, or one named person. An assignment can be edited afterwards without affecting anybody's progress, since training records hang off the person and the course rather than the assignment.
  • Reminder emails chase people as a deadline approaches and after it passes. Off until switched on, under LMS โ†’ Settings โ†’ Reminders. See Training for Portal Users.

Progress tracking

Per learner, per course: status (not started ยท incomplete ยท completed ยท passed ยท failed), score (raw/min/max), total time, bookmark, completion date, attempt count. Authored courses write their quiz results as SCORM cmi.interactions.* rows, so they populate the admin Progress tab and the Learner Data drill-down with no separate code path.

The admin dashboard has four tabs โ€” Courses, Groups, Assignments, Progress. The Progress tab shows every assigned analyst's completion status with overdue highlighting, filterable by course, group, analyst or status, and works identically for both kinds of course. A View button per learner opens the Learner Data viewer: quiz responses with correct/incorrect badges, objectives, scores, suspend data with syntax-highlighted JSON, and every raw CMI element โ€” authored courses populate it too, because they write the same CMI elements.

The four filters compose

Course, group, analyst and status narrow different joins, so they combine rather than override one another โ€” you can ask for one person on one course. Picking an analyst who is not in the selected group correctly returns nothing, rather than one filter quietly winning.

โญ The analyst list is not every analyst in the system: it is built from the same query with the course and group filters applied but not the analyst one, so it lists only the people the current selection actually covers, and choosing somebody does not empty the list you chose them from. A selection that later drops out of the list โ€” a course filter that excludes them โ€” is kept rather than silently reset, because resetting to All analysts would show rows nobody asked for. Added in #1405.

โš ๏ธ Demo data fills this tab (#1404)

Before #1404 the LMS demo module created courses and lessons only โ€” no learning groups, no assignments, no progress โ€” so on a fresh install the Progress tab, which is where a manager spends their time, came up empty and the feature looked unfinished. database/demo-data/lms.json now also creates two groups (All staff, New starters), assigns a course to each, and records progress covering every state the screen renders plus one that is overdue.

That needed is_demo on lms_learning_groups, lms_learning_group_members, lms_course_assignments and lms_progress โ€” the column the importer stamps so it can clear its own rows on a re-import without touching anybody's real ones. Existing installations get the columns from Database Verification.


Key tables

Table Purpose
lms_courses Course metadata โ€” title, content_type (native/scorm), pass_mark, SCORM scorm_version / launch_url, is_active
lms_lessons Ordered lessons of a native course (body = TinyMCE HTML)
lms_questions Questions on a lesson (question_type: single / multiple / truefalse)
lms_answers A question's options; is_correct is the answer key โ€” never sent to a learner
lms_learning_groups Learning groups
lms_learning_group_members Many-to-many group membership
lms_course_assignments Who a course is for, with an optional deadline. target_type says what group_id means: a learning group, a people group, everyone on the portal, or one named person
lms_progress Per-learner per-course status, score, bookmark, times. Keyed on (learner_type, learner_id) โ€” a learner is an analyst or a portal user
lms_reminders_sent The fire-once record for reminder emails, so nobody is reminded twice about the same deadline
lms_cmi_data Raw CMI key/value pairs (SCORM runtime and authored-quiz results)

The three authored-content tables cascade: deleting a course removes its lessons, their questions and those questions' answers, so no orphaned answer key is ever left behind.

Settings

LMS โ†’ Settings holds the AI provider used by the authoring helpers (the lms_ai namespace โ€” see AI Providers; the key is encrypted at rest). Leave it unconfigured and the editor still works โ€” you just author by hand.


Related pages

โš ๏ธ **GitHub.com Fallback** โš ๏ธ