Skip to content

Course Module Overview

The Course module handles everything related to our courses and the gamification of design education.

Hierarchy

  • Course contains 1+ Lessons.
    • Each Lesson contains 1+ LessonItems.
      • Each LessonItem may contain zero or more Quizzes:
        • Multiple-Choice Question (MCQ)
        • Open-Ended Question (OEQ)

Course

A course can be created by an Instructor or a Course Manager (as defined in the Permission module). A created course will not be available for enrollment until it is published. A course may have one or more instructors, marked with a sequence number similar to article author sequences.

When a course is ready, it is scheduled to launch by creating a CourseSchedule. A course may be scheduled as many times as needed. By default, there is only one active CourseSchedule at a time, and another is automatically scheduled to start right after the current one ends. However, special CourseSchedules can be launched as required.

Course States

StateVisibleAuto-scheduleRanking
Draft🔴🔴🔴
Published🟢🟢🟢
DeprecatedPartially*🔴🟢
  • * Visible to enrolled Members only

Course Difficulty Levels

Courses have difficulty levels: beginner, intermediate, and advanced. The course level is defined by the instructor and is used to:

  1. Group courses (mainly for display) on the /courses page.
  2. Automatic course scheduling (see below).

Course Schedules and Auto-Scheduling

Each course can have an indefinite number of CourseSchedules, to which our members can enroll. This means that the course enrollment (CourseEnrollment) is linked to the CourseSchedule, not directly to the Course.

NOTE

The relation between Course and CourseEnrollment exists to enhance performance (denormalization).

There are currently two ways to create a new CourseSchedule:

  1. Scheduled Job: courses:schedule, which processes all auto-scheduled courses and creates new schedules.
  2. Course Admin Panel: Manually create schedules via the Course Admin Panel.

How and Why the App Schedules Courses

The application schedules courses to manage enrollment periods and control when course content becomes available. This allows for cohort-based learning experiences and staggered release of content.

CourseSchedule Model

The CourseSchedule model represents a specific offering of a course with defined start and end dates.

  • Attributes:
    • course_id: References the associated Course.
    • start_date: When the course schedule begins.
    • end_date: When the course schedule ends.
    • is_auto_scheduled: Indicates if the schedule was created automatically.

LessonSchedule Model

When a course is scheduled, each lesson is also scheduled using the LessonSchedule model, determining the availability of lessons.

  • Attributes:
    • lesson_id: References the associated Lesson.
    • course_schedule_id: Associates the lesson schedule with a CourseSchedule.
    • start_date: When the lesson becomes available to enrolled members.

Auto-scheduling Logic

This is the logic used to create the schedules, for each course that is published and has auto-scheduling turned on:

  1. Retrieve the course schedule that has the highest close date
  2. If that course schedule close date is in the future, then do not create a new schedule
  3. If that course schedule close date is less than 3 days ago and the course level is beginner or advanced, do not create a new schedule
  4. If that course schedule close date is less than 2 days ago and the course level is intermediate, do not create a new schedule
  5. Starting from tomorrow, look for the first day that does not have any course of the same level closing on that day
  6. Create a new course schedule starting on the day found on step 5

The resulting effect of applying this logic is:

  1. There will be one, and only one, course of each level starting on each day
  2. As soon as the course closes, a new schedule is created
  3. There will be at most one schedule open for each course
  4. We wait some days after the course is closed in order to schedule it again
  5. There will always be at least one course closed for each level

Percent Booked

The course cards display a "XX% booked" label. This number is currently generated using a semi-random number generator.

Lesson and Lesson Scheduling

A Lesson is a container for multiple LessonItems and includes a description or introduction. Each lesson belongs to one and only one course and has a lesson number, which determines its sequence within the course.

When a course is scheduled, instances of LessonSchedule are created for each lesson, defining their availability to enrolled members.

Lesson Availability

Lessons can be made available either immediately or weekly:

  1. Immediate Lessons: Available right after enrollment (e.g., Lesson 0 and Lesson 1).
  2. Weekly Lessons: Become available each week according to their sequence number.

Lesson Zero

#21169 Document it

Reusing Lessons

Currently, lessons cannot be directly reused across courses. However, we can copy a lesson from one course to another.

#21169 Document it better

LessonItem

A LessonItem is where the actual content of a lesson resides. Depending on the instructor, a lesson may contain one or more lesson items. A LessonItem may contain zero or more Quizzes.

Reusing LessonItems

LessonItems are created for specific lessons and can only be added to one lesson. If added to another lesson, a copy is created to allow independent updates.

Future Enhancements:

  1. Reusing LessonItems by Reference: Allow the same LessonItem to be referenced in multiple lessons.
  2. Skip Already-Completed LessonItems: Enable members to skip lesson items they have already completed in other courses.

Optional vs. Mandatory

LessonItems can be:

  • Mandatory: Contains quizzes; requires completion of all quizzes to mark as complete.
  • Optional: Does not contain quizzes; marked as complete after being viewed.

External Content Provider Types

Most LessonItems contain text, video, and images. Content can be provided by the LI itself or an associated external content provider:

  • article: displays the content of an article attached to the LessonItem.

When the External Content Provider is not specified, Lesson Item's content is used to provide the content (so-call normal Lesson Item type). It is possible to use different shortcodes inside the content of “normal” Lesson Items.

Attached Discussion

Technically, a Discussion can be attached to any type of LessonItem.

Course-specific Shortcodes

There are a few shortcodes exclusively available in courses:

  • Course Certificate (course_certificate): Used in the "Final Lesson" of a course.
  • Evaluation (course_evaluation): Displays the course evaluation form for feedback.
  • Peers Offline (peers_offline): Provides a discussion page for offline interactions.
  • Peers Online (peers_online): Shows related meetups, local groups, etc.
  • Self-Check Information (self_check_info): Prompts the member to update their profile as seen by other course-takers.
  • Next Steps (next_steps): Suggests next steps by build the career (ex-"growth").

Quizzes

A Quiz is mainly a pointer to a question and can be of two types:

  • Multiple-Choice Question (MCQ): Allows three answer choices, with one correct answer.
  • Open-Ended Question (OEQ): Requires a text/html answer, which is graded by a Course Grader.

Each quiz can be used in one LessonItem. Reusing quizzes in different LessonItems is not highly required but can be achieved by copying quizzes.

Quiz Answer

A Quiz Answer is an answer submitted by a member.

Quiz Answer Score

  • MCQs: Always a non-negative integer [0..].
  • Non-Graded OEQs: Always null.
  • Rejected OEQs: Always null.
  • Accepted OEQs: A natural number (0..].

Course Scores

Course scores distinguish between confirmed points and potential points (possible from ungraded OEQs).

  • Confirmed Points: Points actually earned from MCQs or graded OEQs.
  • Potential Points: Maximum possible points from ungraded OEQs.

Scores are stored on the QuizAnswer model. The sum of all scores is stored in the CourseEnrollment to enhance performance.

Recalculating Course Scores

Inconsistencies can occur due to changes in published courses (adding/removing quizzes or LessonItems). Course scores can be recalculated (currently using a Nova action) to resolve discrepancies.

Course Progress

As members answer quizzes, they progress through courses, lessons, and LessonItems. Progress is defined by the number of quizzes answered relative to the total number of quizzes at each level.

The CourseEnrollment keeps track of the number of quizzes answered with submission timestamps as metadata.

Fetching Course Progress

Fetching course progress requires aggregating data from multiple models. To optimize performance:

  • Eager Loading: Utilize Eloquent eager loading to minimize the number of queries.
  • Caching: Implement caching for computed progress percentages.
  • Denormalization: Store computed progress percentages on the CourseEnrollment model where appropriate.

Course Ranks

Course ranks are calculated against others for a whole course, not just a specific schedule.

  • Course Ranks: Display the member's rank on course and LessonItem pages.
  • Ranking Table: Shows ranks of the member and others, accessible via the rank icon on course and LessonItem pages.

Course Certificates

A course certificate is a document certifying that a member has successfully completed a course. It includes:

  • Member's name
  • Course title
  • Course start and end dates
  • Distinction
  • Estimated learning time

Members often share their certificates on social media platforms.

Sample Course Certificates

Sample certificates can be generated for each course to showcase the certificate appearance to prospective enrollees. These samples are localized per visitor's country, using randomly selected male or female names from a maintained list.

Career Programs

Career Programs provide a structured learning path for members to achieve specific career goals through a curated set of Courses and Master Classes. Members can select one Career Program at a time and track their progress toward completion.

Structure

A Career Program consists of:

  • Foundational Courses: Core courses required for the Career Program (marked with is_foundational = true)
  • Regular Courses: Additional courses that complement the foundational knowledge (marked with is_foundational = false)
  • Master Classes: Supplementary Master Classes

Career Program Snapshots

When a member selects a Career Program, a snapshot is created that captures the exact Courses and Master Classes required at that moment. This ensures members can complete the program even if the structure of the Career Program changes later.

The snapshot stores:

  • List of foundational Course IDs
  • List of regular Course IDs
  • List of Master Class IDs
php
$snapshot = $careerProgram->makeSnapshot(); // Returns CareerProgramSnapshot value object

$snapshot->foundationalCourseIds(); // Returns an array of ids
$snapshot->regularCourseIds();  // Returns an array of ids
$snapshot->masterclassIds();  // Returns an array of ids

Member Journey

  1. Selection: Member selects a Career Program (only one can be selected at a time).

    php
    $member->selectCareerProgram($careerProgram);
    $member->getSelectedCareerProgram(); // Returns CareerProgram instance
  2. Progress Tracking: System tracks completion of Courses and Master Classes against the snapshot. The VerifyCareerProgramCompletionStatus action checks if all requirements are met:

    • All Courses (foundational + regular) must be completed
    • All Master Classes must have certificates issued
  3. Completion: When all requirements are met, completed_at timestamp is set to the current time.

Display & Presentation

Career Program data is presented through the CareerProgramData DTO which includes:

  • Career Program name and description (with placeholder replacements)
  • URL of the Career Program, for example: /courses?careerProgram=learn-design-skills-to-become-valuable-in-any-job
  • Collections of foundational Courses, regular Courses, and Master Classes

The GetCareerProgramData action retrieves all data needed to display Career Program, either from:

  • Existing Career Program snapshot
  • Current Career Program relations (for guests or non-selected programs)
php
$careerProgramDataForGuests = (new GetCareerProgramData())->execute($careerProgram);
$careerProgramDataForMember = (new GetCareerProgramData())->execute($careerProgram, $member);

Business Rules

  • Members can only have one selected Career Program at a time
  • Selecting a new program deselects the previous one automatically
  • Completion status is preserved even after deselecting a program
  • Programs use snapshots to ensure stable requirements for enrolled members

Key Classes

  • CareerProgram: Main model representing a Career Program
  • CareerProgramCourse: Pivot model for Course assignments
  • CareerProgramMasterclass: Pivot model for Master Class assignments
  • CareerProgramMember: Pivot model tracking member enrollments to Career Programs
  • CareerProgramSnapshot: Value object storing Career Program snapshot

Events

The Course module dispatches several events to communicate with other modules:

php
use App\Modules\Course\Events\CourseDelete;

Event listeners in other modules can subscribe to these events to perform actions such as awarding badges or sending notifications.

Future Considerations

Event Sourcing

We are considering using Event Sourcing for this module to improve scalability and maintainability.

  • Benefits:
    • Better tracking of state changes.
    • Easier debugging and auditing.
    • Improved scalability for write-heavy operations.

Testing

Course module contains a lot of Models and multi-level hierarchy:

  • Course

    • Lesson (1:m)
      • LessonItem (1:m)
        • Quiz (1:m)
          • MCQ (1:m)
          • OEQ (1:m)
  • Course

    • CourseSchedule (1:m)
      • LessonSchedule (1:m)
  • CourseSchedule

    • CourseEnrollment (1:m)
      • QuizAnswer (1:m)

While you don't always need to create the whole structure in your tests, there are some methods to help you to do at least part of it: There are few methods that will help

php
$course = \Tests\Factories\Course\CourseFactory::new()->withLessonsAndLessonItemsAndQuizzes();

This one creates a Course with a Lessons, LessonItems, and Quizzes.

php
$schedule = \Tests\Factories\Course\CourseScheduleFactory::new()->withLessonSchedules()->for($course)->createOne();

This one will create a CourseSchedule with LessonSchedules for the given Course (the Course should have Lesson(s)).

So, the full setup looks like:

php
use Tests\Factories\Course\CourseFactory;
use Tests\Factories\Course\CourseEnrollmentFactory;
use Tests\Factories\Course\CourseScheduleFactory;
use Tests\Factories\Member\MemberFactory;

$course = CourseFactory::new()->withLessonsAndLessonItemsAndQuizzes();
$schedule = CourseScheduleFactory::new()->withLessonSchedules()->for($course, 'course')->createOne();

return CourseEnrollmentFactory::new()
    ->for(MemberFactory::new()->withMembership())
    ->forCourseSchedule($schedule)
    ->withEmptyMetadata()
    ->createOne();

But again, it's pretty heavy, please minimize the number of Models you create for your tests.