Skip to content

fix(notifications): add unenrollment notification trigger - #3185

Open
faisalahammad wants to merge 3 commits into
gocodebox:devfrom
faisalahammad:fix/3141-unenrollment-notification
Open

fix(notifications): add unenrollment notification trigger#3185
faisalahammad wants to merge 3 commits into
gocodebox:devfrom
faisalahammad:fix/3141-unenrollment-notification

Conversation

@faisalahammad

@faisalahammad faisalahammad commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a new unenrollment notification trigger that fires when a student is removed from a course or membership. Supports both popup (basic) and email delivery, with all subscribers disabled by default so existing sites are not opted in.

Fixes #3141

Description

Adds a new unenrollment notification trigger that fires when a student is removed from a course or membership. Supports both popup (basic) and email delivery, with all subscribers disabled by default so existing sites are not opted in.

How has this been tested?

Test 1: registration

  1. WP Admin → LifterLMS → Settings → Notifications.
  2. Confirm a new "Unenrollment" row appears.

Result: Works as expected.

Test 2: defaults disabled

  1. Open the Unenrollment notification.
  2. Confirm Student, Author, and Custom Email subscribers are all unchecked.

Result: Works as expected.

Test 3: course unenrollment

  1. Enable Student (basic) and Student (email).
  2. Enroll a test student in a course, then unenroll them from the Students tab.
  3. Log in as the student and load a LifterLMS page.

Result: popup appears with the course title and the negative icon; the student receives the email.

Test 4: membership unenrollment

  1. Repeat Test 3 with a membership.

Result: same popup and email behavior, with {{TYPE}} resolving to "Membership".

Test 5: silent on hard delete

  1. Programmatically call llms_delete_student_enrollment() which fires the llms_user_enrollment_deleted hook (not the unenrollment hook).

Result: no notification is sent.

Screenshots

CLI/no UI change. No visual change to existing screens; a new "Unenrollment" row appears in LifterLMS → Settings → Notifications.

Types of changes

  • New feature (non-breaking change which adds functionality)

Checklist

  • This PR requires and contains at least one changelog file (.changelogs/issue-3141.yml).
  • My code has been tested.
  • My code passes all existing automated tests (composer run-script check-cs-errors clean).
  • My code follows the LifterLMS Coding & Documentation Standards.

Changes

includes/notifications/controllers/class.llms.notification.controller.unenrollment.php (new)

Before: no controller existed for the unenrollment event.

After:

class LLMS_Notification_Controller_Unenrollment extends LLMS_Abstract_Notification_Controller {
    public $id = 'unenrollment';
    protected $action_accepted_args = 4;
    protected $action_hooks = array(
        'llms_user_removed_from_course',
        'llms_user_removed_from_membership',
    );
    // action_callback() captures user_id + post_id and dispatches the notification.
    // get_subscriber() resolves 'author' (course/membership author) and 'student' (the unenrolled user).
    // set_subscriber_options() returns 'no' defaults for all subscribers.
}

Why: mirrors the existing enrollment controller pattern so admins configure one notification for both post types. Uses llms_user_removed_from_membership rather than the _level form (the _level hook was removed in 6.0.0). Accepts 4 args because the unenrollment action passes $user_id, $post_id, $trigger, $new_status.

includes/notifications/views/class.llms.notification.view.unenrollment.php (new)

Before: no view existed for the unenrollment event.

After:

class LLMS_Notification_View_Unenrollment extends LLMS_Abstract_Notification_View {
    public $trigger_id = 'unenrollment';
    protected $basic_options = array( 'auto_dismiss' => 10000, 'dismissible' => true );
    // Merge codes: {{TITLE}}, {{TYPE}}, {{STUDENT_NAME}}
    // Icon: negative variant.
    // Body (popup): "{{STUDENT_NAME}} has been unenrolled from {{TITLE}}."
    // Body (email): "You have been unenrolled from {{TITLE}}."
    // Subject (self): "You have been unenrolled from {{TITLE}}"
    // Subject (other): "{{STUDENT_NAME}} unenrolled from {{TITLE}}"
    // Title: "{{TYPE}} unenrollment"
}

Why: same merge-code and formatting conventions as the enrollment view. Body and subject branch on notification type and is_for_self() so the recipient is addressed directly when the notification is for them. Negative icon visually signals removal. The popup auto-dismisses and is manually dismissible like the rest of the basic notifications.

includes/notifications/class.llms.notifications.php

Before: $triggers array ended with subscription_cancelled followed by upcoming_payment_reminder.

After: 'unenrollment' inserted between them to keep alphabetical order.

Why: the loader pulls the controller and view from their conventional paths once the trigger is registered, so no other registration is needed.

.changelogs/issue-3141.yml (new)

significance: minor
type: added
entry: "Added unenrollment notification trigger (popup and email) for courses and memberships, disabled by default."

Why: the project's changelog tooling only picks up files ending in .yml; this matches the contribution workflow.

Adds a new 'unenrollment' notification that fires when a student is removed
from a course or membership. Supports popup (basic) and email delivery, with
all subscribers disabled by default per the feature request.

- New controller listens to llms_user_removed_from_course and
  llms_user_removed_from_membership (the post 6.0.0 hooks).
- New view defines {{TITLE}}, {{TYPE}}, and {{STUDENT_NAME}} merge codes
  with a negative icon variant.
- Register 'unenrollment' trigger in LLMS_Notifications::load().
- All subscribers default to 'no' so existing sites are not opted in.

Fixes gocodebox#3141
@faisalahammad
faisalahammad requested a review from brianhogg as a code owner June 15, 2026 20:38
@brianhogg brianhogg moved this to Awaiting Review in Development Jun 15, 2026
@github-project-automation github-project-automation Bot moved this from Awaiting Review to To do in Development Jul 21, 2026
@brianhogg brianhogg moved this from To do to Review in Progress in Development Jul 21, 2026
@brianhogg brianhogg moved this from Review in Progress to To do in Development Jul 21, 2026
…pient

- set_body() now returns 'You have been unenrolled from {{TITLE}}.' for
  email and '{{STUDENT_NAME}} has been unenrolled from {{TITLE}}.' for
  popup, matching the pattern in
  class.llms.notification.view.course.complete.php:53.
- set_subject() now returns 'You have been unenrolled from {{TITLE}}'
  for self and '{{STUDENT_NAME}} unenrolled from {{TITLE}}' for others,
  using is_for_self() like the existing merge-data handler.

Addresses PR gocodebox#3185 feedback.

Refs gocodebox#3141
@faisalahammad

faisalahammad commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

@brianhogg re-review requested.

Replied to all 6 review threads.

Followed your direction on the @Version tags. Pushed:

  • ce4c65e: view changes (set_body branches on email/popup, set_subject branches on is_for_self).
  • 70daab2: dropped @Version [version] from all 4 controller method docblocks and all 7 view method docblocks. Only the file-level header @Version [version] is kept on each file.

Sorry for the back-and-forth on the docblock tags.

… files

Removes @Version [version] from all 4 method docblocks in the controller
and all 7 method docblocks in the view, leaving only the file-level header
@Version. Per maintainer direction.

Addresses PR gocodebox#3185 feedback.

Refs gocodebox#3141
@brianhogg

Copy link
Copy Markdown
Contributor

@brianhogg re-review requested.

Replied to all 6 review threads.

Followed your direction on the @Version tags. Pushed:

Sorry for the back-and-forth on the docblock tags.

No worries re: the docblock tags! It is a change, and I agree it might be good to just blanket update everything one way or the other at some point. But with the per-file scans on wp.org, that won't be anytime soon :)

@faisalahammad

Copy link
Copy Markdown
Contributor Author

Thanks, @brianhogg! I appreciate your understanding. Yes, it would be nice to have a consistent approach to the docblock tags someday. For now, this should keep things cleaner and easier to manage. Looking forward to your further feedback!

@brianhogg brianhogg moved this from To do to Review in Progress in Development Jul 23, 2026
@brianhogg brianhogg added this to the 10.2 milestone Aug 7, 2026
@brianhogg brianhogg moved this from Review in Progress to Awaiting Review in Development Aug 7, 2026
@brianhogg brianhogg modified the milestones: 10.2, 10.3 Aug 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Awaiting Review

Development

Successfully merging this pull request may close these issues.

2 participants