Proposal for course revisioning strategy

Created on 14 June 2025, 20 days ago

Problem/Motivation

The LMS module already includes data integrity measures to protect student progress from edited course content, which helps make sure that changes to lessons or activities don't alter the context for students already taking a course.

But the current approach can prevent course creators from continually improving courses after students have started them, which may be a requirement for online courses that have continuous enrollment. Any time a course creator needs to make a significant update or correction, they may have to duplicate the course and republish it β€” which has its own complications for current students, and for previous students who want to revisit their answers.

The goal of this proposal is to allow new versions of courses, lessons, and activities to be created and published at any time, without affecting students in progress, who will seamlessly continue with the versions they started with.
 

Proposed Resolution

We can implement a fully-dynamic course version system by using entity revisioning. The main concept is to link student progress entities (CourseStatus, LessonStatus, and Answer) to specific revisions of their content entities (Course, Lesson, and Activity).

Newly enrolling students will get the latest published revisions of a course and its components. They will then be locked in to those revisions for their course, evaluation, and revisits.

This will create a clear and reviewable history of content changes, and keep each student's learning path consistent with the materials they started with.
 

Benefits:

  • Continuous Improvement: Course creators can update, refine, and correct course materials at any time, knowing that students currently in progress will not be affected.
     
  • Improved Data Integrity: Builds on the existing data protection by making sure each student always interacts with a specific version of the learning materials.
     
  • Better Content Management: Provides a full revision history for all learning content, allowing for easy review, comparison, and rollback if necessary. Also allows course creators to collaborate in Workspaces and publish changes when ready.
     
  • Admin Efficiency: Minimizes the need to duplicate entire courses, or other workaround strategies, when updates are needed.
     
  • Fairness and Consistency: Students are evaluated based on the version of the course, lessons, and activities that they started with β€“ including scoring criteria like max points or required percentages β€“ even if content is updated later. And they can always revisit the exact same activities they originally started with.
     
  • Architectural Excellence: By using Drupal's native revisioning, this feature has the potential to position Drupal LMS as one of the best-architected learning management systems available, offering a higher level of flexibility, editability, and content control than any other LMS platform.
     

Phased Implementation Plan

If this proposal is agreed upon, it will be a significant update, but it can be broken down into phases. Each phase will need to maintain full module functionality, include automated testing, and provide an upgrade path for existing sites.

Phase 1: Enable Revisioning & Basic Referencing

  • All of the Course (Group), Lesson, and Activity entity types need to be set up for revisioning (new_revision: true, appropriate revision metadata keys).
     
  • Add new *_vid (integer) fields to CourseStatus, LessonStatus, and Answer entities to store the target revision IDs.
     
  • Create update hooks (hook_update_N) to:
    • Apply schema changes for the new *_vid fields.
    • For existing entities, ensure they have an initial revision.
    • For existing CourseStatus, LessonStatus, and Answer records, populate the new *_vid fields to point to the (then) current/latest revision of their referenced content. This establishes a baseline for existing in-progress courses.
       

Phase 2: Save Revisions & Reference vid on Creation

  • Modify entity forms (Course, Lesson, Activity) to default to "Create new revision" on save, and include instructions on the effects of revisions.
     
  • Update TrainingManager's creation logic (e.g., initializeTraining(), initializeLesson(), createAnswer()) to store the vid of the latest published revision of the parent/referenced entity into the new *_vid fields when new student progress records are created.
     

Phase 3: Load Specific Revisions for Student Progress

  • Refactor TrainingManager, status entities (CourseStatus, LessonStatus), AnswerForm, CourseController, BlockBuilder, and any other relevant services/controllers to load specific entity revisions (using loadRevision($vid)) based on the *_vid fields stored in the student's progress records.
     
  • Make sure that the lessons for a course, and the activities for a lesson, are populated from their specific parent's revisions.
     
  • Implement revision-aware cache tags and contexts for all relevant display components (e.g., course navigation block, activity views, start links).
     
  • Add functional testing: Student A starts Course v1. Admin edits content, creating Course v2. Student A continues their course interacting only with v1 content. Student B enrolls and starts Course v2, interacting only with v2 content.
     

Phase 4: Deletion Protection & UI/UX Refinements

  • Add access checks or entity hooks to prevent the deletion of entity revisions that are actively referenced by student progress entities.
     
  • Enhance DataIntegrityChecker and entity delete forms (ActivityDeleteForm, LessonDeleteForm) to account for revision references.
     
  • Make sure admins can view, compare, and revert revisions for Courses, Lessons, and Activities.
     
  • Refine rules and UI for deleting old, unreferenced revisions versus revisions actively in use.
     

Phase 5: Additional Enhancements

  • Serve Freshest Unaccessed Content: if an edit is made to a lesson or activity that a student hasn't reached yet, they could be served the newest version. May be valuable for long courses.
     
  • Data Archival/Expiration: Implement strategies for managing progress data of students who have been inactive for an extended period.
     

Discussion

Please add your thoughts, concerns, optimizations, fears, and/or excitement!
 

🌱 Plan
Status

Active

Version

1.0

Component

Courses and lessons

Created by

πŸ‡¨πŸ‡¦Canada ob3ron Canada

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

  • Issue created by @ob3ron
  • πŸ‡ΊπŸ‡ΈUnited States moshe weitzman Boston, MA

    Drupal Commerce has a similar need. When you buy a product, it should be frozen in time, and not subject to future edits. I’m not sure if they solve this by refusing. Worth a look.

    In general this proposal makes sense to me. There are.a couple downsides. Namely teachers Can’t easily fix typos in a course and have that affect students in progress. Same for scoring. Also we could see revision bloat similar to what happens on sites that use paragraphs module.

  • πŸ‡¨πŸ‡¦Canada ob3ron Canada

    @moshe weitzman thanks for the feedback, much appreciated.

    The thinking is that for something like a typo, teachers/admins can submit an edit and not create a new revision, in which case the change will be pushed live to current students.

    Revisions shouldn't proliferate too badly since it will be a conscious choice to make a new one, and we can implement processes to safely delete old unused revisions.

    I'll check out the Commerce approach, thanks for that tip!

  • πŸ‡¬πŸ‡§United Kingdom catch

    The thinking is that for something like a typo, teachers/admins can submit an edit and not create a new revision, in which case the change will be pushed live to current students.

    I think this is a bit too subtle to expect course authors to understand the implications of or get right all the time even if they do - if you're just doing a quick fix, the default behaviour would be to fix the typo and hit save. Once revisions default to on, that will result in a new revision, you have to take extra steps to avoid it.

    However the issue summary doesn't explicitly mention that e.g. lessons would reference specific versions of activities (which is the only way older versions could be loaded), and I'm not sure it's necessary.

    If e.g. a lesson status references a specific revision of a lesson, then the list of activities will differ by version, and it's that which would allow activities to be added or removed from lessons without affecting students in progress.

    Similar if a course status references a specific revision of a course, the list of lessons will differ by version.

    https://www.drupal.org/project/entity_reference_revisions β†’

    There's another issue though that something like adding a lesson at the end of a course where no-one has reached it yet, you might want everyone to take that lesson

  • πŸ‡΅πŸ‡±Poland Graber

    We could make a global setting so the site owner can choose if students take the course / lesson versions from the moment of starting or current, that'd also solve any possible BC issues.

  • πŸ‡¬πŸ‡§United Kingdom catch

    I think we could do phase 1 without the exact details of what phase 2/3 look like - we definitely want answers, lesson status, and course status to reference revisions. Means switching the entity reference field over to https://www.drupal.org/project/entity_reference_revisions β†’

  • πŸ‡¨πŸ‡¦Canada ob3ron Canada

    Can we start off by adding *_vid fields to CourseStatus, LessonStatus, and Answer entities and run an initial update hook, then transition to Entity Reference Revisions when we're ready to migrate the Course.lessons and Lesson.activities fields? Seems like that could make the updates less monolithic.

    Drupal Commerce has a similar need...I’m not sure if they solve this by revisions.

    Turns out Commerce copies the current product data into an order item. Same need but not really a transferable solution.

    I think [the revision checkbox] is a bit too subtle to expect course authors to understand the implications of or get right all the time even if they do

    The revision checkbox could have explicit instructions marked "IMPORTANT". Or we could alter the form to have two submit buttons, one for a minor live update, the other for a new version. Either way I agree it's hard to make it foolproof. Needs good documentation.

    something like adding a lesson at the end of a course where no-one has reached it yet, you might want everyone to take that lesson

    The "Freshest Unaccessed Content" feature would address this. It adds some complexity to the lock-in timing, but seems worthwhile for long courses.

    We could make a global setting so the site owner can choose if students take the course / lesson versions from the moment of starting or current

    If set to "current" then we'd lose consistency with evaluation & revisits, unless we associate revision lists with uid. The revision schema updates add a BC layer, so not sure if this would add more complication than it's worth.

  • πŸ‡΅πŸ‡±Poland Graber

    Just a β€œvid” field will not be enough, for example lesson status references multiple activities, that’d be too messy. I also wouldn’t like to add an additional dependency but we can use some of the Entity Reference Revisions code as a reference - a new field type plugin should be enough in our case.
    Update may be a bit tricky.

  • πŸ‡΅πŸ‡±Poland Graber

    Actually.. maybe it’s not going to be so bad as we’ll just need to add a new column to an existing schema, reference field names will not change after all.

  • πŸ‡΅πŸ‡±Poland Graber
  • πŸ‡΅πŸ‡±Poland Graber

    This will be bigger as we'll have to reference lesson revisions from course status as we currently reference activities from lesson status. The good thing is consistency though. That should be the first step of the work here. The first step alone will only introduce needless data storage though.

  • πŸ‡΅πŸ‡±Poland Graber

    This will also affect caching a lot - nav block will depend on course status lessons and not course lessons. Course nav structure cache will have to be dropped.

  • πŸ‡΅πŸ‡±Poland Graber
  • πŸ‡¬πŸ‡§United Kingdom catch

    For #12 I think we might want to make this configurable so that you only get a per-course-status navigation structure/activity display when the site owner explicitly wants that - I can equally see people being confused why they can't fix a typo in an activity name for existing users.

    Also we already have logic to cache the navigation block differently depending on whether modules are finished, in progress, or not taken yet so it should be possible to preserve some or most of that.

Production build 0.71.5 2024