Change record status: 
Project: 
Introduced in branch: 
1.2.x
Introduced in version: 
1.2.0-alpha1
Description: 

Previously, the lms_course group bundle was hard-coded throughout the LMS module. As of LMS 1.2.0, any group bundle whose bundle class implements \Drupal\lms\Entity\Bundle\CourseInterface is treated as a course, so a site can have multiple course bundles with different fields and configuration.

Registering an additional course bundle

Create a group type, then assign it a bundle class that implements CourseInterface (extending the provided Course class is the easiest way):

/**
 * Implements hook_entity_bundle_info_alter().
 */
function mymodule_entity_bundle_info_alter(array &$bundles): void {
  if (isset($bundles['group']['my_course_type'])) {
    $bundles['group']['my_course_type']['class'] = \Drupal\mymodule\Entity\Bundle\MyCourse::class;
  }
}

Course routes now restrict their {group} parameter to course bundles dynamically in route subscribers (previously static YAML), so a router rebuild is required after registering or removing a course bundle.

New API

  • \Drupal\lms\Entity\Bundle\CourseInterface — the contract for course bundle classes. The Course::LESSONS constant now lives on the interface (still accessible as Course::LESSONS).
  • TrainingManager::getCourseBundleIds() — returns all group bundle IDs whose bundle class implements CourseInterface (statically cached per request).
  • TrainingManager::loadGroupRelationshipsByCourseBundles(array $properties) — wrapper around loadByProperties() that filters to course bundles. Use it instead of hard-coding 'group_type' => 'lms_course'.

Backwards-incompatible changes

1. Type hints changed from Course to CourseInterface in public method signatures across the module, including TrainingManager, CourseController, ClassHelper, ClassNameGenerator and the hook_lms_course_link() hook. Code calling these methods keeps working; hook implementations and overrides must update their signatures:

// Before:
function mymodule_lms_course_link(Course $course, AccountInterface $current_user, CacheableMetadata $cacheability): array {}

// After:
function mymodule_lms_course_link(CourseInterface $course, AccountInterface $current_user, CacheableMetadata $cacheability): array {}

If your custom code checks $group instanceof Course, switch to $group instanceof CourseInterface so additional course bundles are recognized.

2. CourseStatusInterface::getCourse() no longer returns NULL. The return type changed from ?Course to CourseInterface. The implementation already threw \Drupal\lms\Exception\TrainingException when the course was missing, so the interface now matches reality — but custom code with NULL checks must switch to catching the exception:

// Before:
$course = $course_status->getCourse();
if ($course === NULL) {
  // Orphaned status.
}

// After:
try {
  $course = $course_status->getCourse();
}
catch (TrainingException $e) {
  // Orphaned status.
}

3. Course navigation block classes renamed. \Drupal\lms\BlockBuilder is now \Drupal\lms\CourseNavBlockBuilder and the lms.block_builder service ID was removed — inject Drupal\lms\CourseNavBlockBuilder instead. The block plugin class StepsBlock is now CourseNavBlock; the plugin ID course_steps_block is unchanged, so existing block placements are unaffected.

4. Class-related Views integration moved to the lms_classes submodule. The ClassMemberCourseStatus views relationship plugin moved from Drupal\lms\Plugin\views\relationship to Drupal\lms_classes\Plugin\views\relationship, and the classes_filter / course_status views data definitions (with the lms_parent_class filter schema) moved from lms to lms_classes. Plugin IDs are unchanged; views using these handlers now require lms_classes to be enabled (they only ever worked with classes).

5. Bundle restrictions removed from routing YAML. The static bundle: [lms_course] parameter options were removed from lms.routing.yml and lms_classes.routing.yml and are now applied dynamically by RouteSubscriber and LmsClassesRouteSubscriber. If you altered these routes, review your alterations.

Impacts: 
Site builders, administrators, editors
Module developers