Upgrading to ME v28.0

Upgrading Guide for the release for v28.0

Important Upgrade Notes

Release 1.28 contains version changes that are important to note:

  • Laravel Update from 9.x -> 11.x

  • beyondcode/laravel-websockets -> Laravel/Reverb

  • Laminas Replacements

  • Node.js version update from 14.18.3 -> 18.x and higher

  • Moment.js -> day.js

  • ADODB 5.21.* -> 5.22.*

Please review the Important Changes section before proceeding.

Upgrading to v28.0

For traditonal upgrading see: Part 1: Source Code Update

Incremental upgrading

Elentra ME v28.0 has been tagged continuously during development process which allows the ability to upgrade incrementally instead of all at once.

There are 48 patches releases that contain the pattern of v28.0-rc.X . Upgrading incrementally allows to upgrade patch by patch or to upgrade by multiple patches.

There are two types of patch releases, Technical and Product patch releases. Product patch releases contains a title of module worked on:

v28.0-rc.44 Exam Improvements

Each important upgrades will contain a patch release number to identify when the change has been merged in.

Important Changes - Library Upgrades

This release contains significant changes, and you will need to regression test all of your customisation in case these updates break them.

Laravel 9.x -> 11.x

Laravel 9.x → 10.x: Elentra ME v28.0-rc.3

Laravel 10.x upgrade requires change to DB::raw See: https://github.com/ElentraProject/elentra-1x-api/pull/1079

Laravel 10.x → 11.x: Elentra ME v28.0-rc.43

Laravel Future Support:

Version
PHP (*)
Release
Bug Fixes Until
Security Fixes Until

9

8.0 - 8.2

February 8th, 2022

August 8th, 2023

February 6th, 2024

10

8.1 - 8.3

February 14th, 2023

August 6th, 2024

February 4th, 2025

11

8.2 - 8.4

March 12th, 2024

September 3rd, 2025

March 12th, 2026

12

8.2 - 8.4

Q1 2025

Q3 2026

Q1 2027

Laravel Web-sockets

Changes were made to remove the web-socket library beyondcode/laravel-websockets which has been abandoned by the maintainers. Please See: https://github.com/beyondcode/laravel-websockets

To resolve this issue, beyondcode/laravel-websockets has been replaced with Laravel Reverb which has been released March 12th 2024.

This change requires update across all Elentra repositories.

ME: Elentra ME v28.0-rc.17 API: v11.0-rc.6 EJS2: v2.7.4 DEV: v5.0.3

Configuration notes

In general, the configuration should be managed exclusively through the www-root/core/config/settings.inc.php file. This means you don't need to worry about the .env files in elentra-1x-api and elentra-2x-js. The values in the .env files will be ignored if corresponding values are set in settings.inc.php.

// Public websocket settings
define("WEBSOCKETS_AUTH_ENDPOINT", "/api/v2/broadcasting/auth"); // Endpoint for authenticating websocket connections
define("WEBSOCKETS_SERVER", "localhost");                        // Hostname or address of the websocket server  e.g.: elentra.med.university.edu                                                      
define("WEBSOCKETS_PORT", 6001);                                 // Port number for websocket connections Note: Default port is usually 443 for HTTPS
// Server websocket settings: specify the host and port on which to run the websockets server (reverb by default) itself, (usually 0.0.0.0:6001)
define("WEBSOCKETS_SERVER_HOST", "0.0.0.0");                    
define("WEBSOCKETS_SERVER_PORT", 6001);
define("WEBSOCKETS_SCHEME", "http");                            // Scheme used for websocket connections Note: Consider using 'https' for secure websocket connections
define("WEBSOCKETS_CLUSTER", "");                                      
// Websocket's app credentials (you may leave them as they are for development mode)
define("WEBSOCKETS_APP_ID", AUTH_APP_ID);
define("WEBSOCKETS_APP_KEY", "elentra-pusher-key");
define("WEBSOCKETS_APP_SECRET", "elentra-app-secret");

Note: Do not confuse WEBSOCKETS_SERVER and WEBSOCKETS_PORT with WEBSOCKETS_SERVER_HOST and WEBSOCKETS_PORT. The former (WEBSOCKETS_SERVER and WEBSOCKETS_PORT) are for broadcasting the WebSocket, while the latter (WEBSOCKETS_SERVER_HOST and WEBSOCKETS_PORT) are used for serving the Reverb server, which is triggered by the php artisan reverb:start command.

Setup Instructions

Before running the php artisan reverb:start command, ensure you execute the following commands, assuming you have an up-to-date branch:

cd /var/www/vhosts/elentra-1x-me
composer install
composer update
cd /var/www/vhosts/elentra-1x-me/www-root/core/library/vendor/elentrapackages/elentra-1x-api
composer install
composer update
php artisan cache:clear
php artisan optimize:clear
php artisan reverb:start

Production mode notes:

  • Ensure WEBSOCKETS_SERVER is set to the appropriate server. For example, if your site is http://www.example.com, change localhost to example.com.

  • Set WEBSOCKETS_PORT to 443 if you are using HTTPS.

  • Change WEBSOCKETS_SCHEME to https if you are using HTTPS.

Laminas

Elentra ME has been using Laminas/Zend for a very long time that offers the same features that Laravel also supports. There was no need to have two frameworks that offer the same features, therefore v28.0 has removed Laminas libraries that can be replaced.

Replace Laminas Cache

Patch: Elentra ME v28.0-rc.4

Laminas cache was replaced with Laravel Cache by creating a wrapper around the Laminas Cache methods to ensure that no invasive changes are needed

Replace Laminas Feed and Laminas HTTP

Patch: Elentra ME v28.0-rc.4

Laminas Feed and Laminas HTTP were being used for RSS feeds that are rendered on the dashboard. This was replaced with vedmant/laravel-feed-reader on Elentra-1x-api.

Remove Laminas JSON and Laminas MATH

Laminas JSON was being used decode JSON which was replace with PHP native method of json_decode.

Laminas MATH was not being used within the application and can be removed

Node.js

Patch: Elentra ME v28.0-rc.1

Elentra EJS2 now supports Node.js version 18 and higher. Ensure that custom added libraries are compatible with Node.js version 18 and higher

Moment.js

Patch: Elentra ME v28.0-rc.8

Moment.js has been deprecated and was replace by days.js. The change requires to replace moment() with dayjs()

- var start_date = moment(state.min_date).unix();
- var end_date = moment(state.max_date).subtract(1, 'second').unix();
+ var start_date = dayjs(state.min_date).unix();
+ var end_date = dayjs(state.max_date).subtract(1, 'second').unix();

Ensure that any uses of momont.js is replaced with dayjs

ADODB

Paches: Elentra ME v28.0-rc.40, Elentra ME v28.0-rc.43

There are significant changes with the ADODB upgrade from 5.21.* to 5.22.* that contains error handing and data integrity checks within the base class. These changes require a large amount of attention to ensure that current functionality is not impacted. There is a new setting called “STRICT_MODE” which allows developers to turn off some of the checks, it is highly recommended to not turn this off in developer mode.

Invalid Constraints

A new data integrity check is to ensure that all constraint columns must have an associated class attribute with same name. Code Snippet of Check:

if (!in_array($constraint_key, $class_vars)) { 
    throw new Exception("Invalid constraint key ". $constraint_key ." for ". __FUNCTION__ ." in ". get_called_class(). " model."); 
}

Example Error

Example Class:

class myModel extends Models_Base { 
    private $id, $name; 
}

Example Table:

id | name | deleted_date

Example call:

return $self->fetchRow(array(
    array("key" => "id", "value" => $id, "method" => "="),
    array("key" => "name", "value" => $name, "method" => "="),
    array("key" => "deleted_date", "value" => null, "method" => "IS")
));

Since delete_date is not a class attribute of myModel class, an error is rendered.

Example of resolving the error:

class myModel extends Models_Base {
   private $id,
           $name,
           $delete_date;
}

No Constraints

This check is to ensure that fetchAll and fetchRow have constraints passed as a non-empty array or as an empty value. fetchAll without a constraint should use getAll ADODB method instead.

Code Snippet of Check:

if (!is_array($constraints) || empty($constraints)) {
    throw new Exception("No constraints provided for ". __FUNCTION__ ." in ". get_called_class(). " model.");
}

Example Error:

return $self->fetchRow();

Failed Query

Query failure check has been implemented that will throw an error if the query fails and the exception returns the database error message back for logging. Query failures originally would return false then pass the boolean up the stack which cause an ambiguous datatype between no results and query fail.

Code Snippet of Check:

if ($result === false && $db->ErrorMsg()) {
    throw new Exception("Error fetching row ". get_called_class(). " record: " . $db->ErrorMsg());
}

This might uncover previously unknown failing queries as a widely used pattern in Elentra is to pass false for no results but also pass false to failing queries.

Constraint and Value EQ Check

A query integrity check was added to ensure that no queries is executed with an = constraint using value of null. The reason for this is that any query that runs with an = constraint with null value will always return an empty results therefore should use IS NULL or IS NOT NULL instead.

Code Snippet of Check:

if (is_null($value)) {
    Models_Base::triggerStrict("Value cannot be null, use `IS` or `IS NOT` instead.");
}

Example Error:

$name = null;
return $self->fetchRow(array(
    array("key" => "id", "value" => $id, "method" => "="),
    array("key" => "name", "value" => $name, "method" => "=")
));

Example of resolving the error:

Resolving this error is more difficult and requires investigations since a condition of the query doesn’t contain a value.

// Example of the most common issue:
$results = $self->fetchRow(array(
     array("key" => "deleted_date", "value" => null, "method" => "=")
));
// Should be:
$results = $self->fetchRow(array(
     array("key" => "deleted_date", "value" => null, "method" => "IS")
));
// Results will be an empty result

Failed Query Throws an Exception

With Strict Mode on, any ADODB query failure will throw an exception that will render an error message to the end user.

if (defined("STRICT_MODE") && STRICT_MODE) {
    require_once(ENTRADA_ABSOLUTE . "/core/includes/database-exception-handler.inc.php");
    $db->raiseErrorFn = 'database_exception_handler';
}

Consortium Members can edit the exception behaviour by editing database-exception-handler.inc.php

<?php
/*
    This method is used to handle database exceptions. It is called by the database class when an exception is thrown.
    The method will throw a new exception with the error message and error number.
*/
function database_exception_handler(
    string $dbms, 
    string $fn,
    int $errno,
    string $errmsg,
    string $p1, 
    array|bool $p2, 
    object &$thisConnection
) {
    // Falls back to the default error handler
    throw new Exception(
        "Database Exception: " . $dbms . " error in " . $fn . "(): " . $errmsg . " (" . $errno . ")",
        $errno
    );
}

Strict Mode

Strict mode was introduced to prevent an overwhelming amount of fatal errors during production. If a developer determines that an exception should under a strict mode can call the method triggerStrict in order to prevent the exception.

private static function triggerStrict($message) {
    application_log("error", $message);
    if (defined("STRICT_MODE") && STRICT_MODE) {
        throw new Exception($message);
    }
}

Important Changes - Architectural Changes

Replacing Prototype with JQuery

Patches: Elentra ME v28.0-rc.1, Elentra ME v28.0-rc.2, Elentra ME v28.0-rc.3

Prototype is a JS library that has been part of the Elentra application since the first release. The library has not been updated since Sept. 2015 which the library scriptaculous is dependant on also hasn’t been updated from Dec. 2010.

Prototype will override JS event listeners of “show” and “hide” that bootstrap relies on. For this reason, a effort was made forward to remove prototype and scriptaculous from Elentra ME. Since Prototype is used throughout the application, additional logic was added on spa.head.php file:

<?php
    $url = $_SERVER['REQUEST_URI'];
    // Admin Events Page has removed Prototype therefore DO NOT include it
    $include_scripts = true;
    $excluded_pages = ['/events', '/admin/events', '/dashboard', '/communities'];
    foreach ($excluded_pages as $page) {
        if (strncmp($url, $page, strlen($page)) === 0) {
            $include_scripts = false; // Don't include the scripts if the current page matches any excluded page
            break;
        }
    }
    // Include the scripts if the flag is still
    if ($include_scripts) {
        ?>
            <!-- Legacy Prototype -->
            <script src="<?php echo script(ENTRADA_RELATIVE . '/javascript/scriptaculous/prototype.js'); ?>"></script>
            <script src="<?php echo script(ENTRADA_RELATIVE . '/javascript/scriptaculous/scriptaculous.js'); ?>"></script>
            <script src="<?php echo script(ENTRADA_RELATIVE . '/javascript/livepipe/livepipe.js'); ?>"></script>
            <script src="<?php echo script(ENTRADA_RELATIVE . '/javascript/livepipe/window.js'); ?>"></script>
        <?php
    }
?>

When a page does not have prototype, it’s url is added to the list of excluded pages so that prototype can be slowly removed from Elentra.

Remove unused JS Files

Patches: Elentra ME v28.0-rc.11, Elentra ME v28.0-rc.12, Elentra ME v28.0-rc.15, Elentra ME v28.0-rc.16, Elentra ME v28.0-rc.19

List of removed files:

Remove unused API ednpoints

Patches: Elentra ME v28.0-rc.14, Elentra ME v28.0-rc.16

List of files removed:

API Consolidation

Patches: Elentra ME v28.0-rc.11, Elentra ME v28.0-rc.12, Elentra ME v28.0-rc.19, Elentra Me v28.0-rc.23, Elentra ME v28.0-rc.26

List of files moved:

  1. www-root/core/modules/admin/assessments/blueprints/api-blueprints.inc.php -> www-root/api/blueprints.php [Elentra ME v28.0-rc.11]

  2. www-root/core/modules/admin/assessments/cbmeforms/api-forms.inc.php → www-root/api/assessments/cbmeforms/forms.php [Elentra ME v28.0-rc.12]

  3. www-root/core/modules/admin/assessments/cbmeforms/api-list.inc.php → www-root/api/assessments/cbmeforms/list.php [Elentra ME v28.0-rc.12]

  4. www-root/core/modules/admin/assessments/distributions/api-distributions.inc.php → www-root/api/assessments/distributions/distributions.php [Elentra ME v28.0-rc.19]

  5. www-root/core/modules/admin/assessments/forms/api-forms.inc.php → www-root/api/assessments/forms/forms.php [Elentra Me v28.0-rc.23]

  6. www-root/core/modules/admin/assessments/items/api-items.inc.php → www-root/api/assessments/items/items.php [Elentra Me v28.0-rc.23]

  7. www-root/core/modules/admin/assessments/scales/api-scales.inc.php → www-root/api/assessments/scales/scales.php [Elentra Me v28.0-rc.23]

  8. www-root/core/modules/admin/assessments/rubrics/api-rubric.inc.php → www-root/api/assessments/rubrics/rubrics.php [Elentra ME v28.0-rc.26]

  9. www-root/core/modules/admin/assessments/portfolios/api-portfolios.inc.php → www-root/api/assessments/portfolios/portfolios.php [Elentra ME v28.0-rc.26]

  10. www-root/core/modules/admin/assessments/api-assessment-reports.inc.php → www-root/api/assessments/assessment-reports.php [Elentra ME v28.0-rc.43]

  11. www-root/core/modules/admin/assessments/api-dashboard.inc.php → www-root/api/assessments/dashboard.php [Elentra ME v28.0-rc.43]

  12. www-root/core/modules/admin/assessments/api-evaluation-reports.inc.php → www-root/api/assessments/evaluation-reports.php [Elentra ME v28.0-rc.43]

  13. www-root/core/modules/admin/assessments/api-tools-feedbacks-reports.inc.php → www-root/api/assessments/tools-feedbacks-reports.php [Elentra ME v28.0-rc.43]

  14. www-root/core/modules/admin/assessments/api-user-photo-upload.inc.php → www-root/api/assessments/user-photo-upload.php [Elentra ME v28.0-rc.43]

  15. www-root/core/modules/admin/courses/api-courses.inc.php → www-root/api/courses/courses.php [Elentra ME v28.0-rc.43]

  16. www-root/core/modules/admin/courses/api-image.inc.php → www-root/api/courses/image.php [Elentra ME v28.0-rc.43]

  17. www-root/core/modules/admin/awards/api-awards.inc.php -> www-root/api/awards/awards.php [Elentra ME v28.0-rc.43]

  18. www-root/core/modules/admin/courses/cbme/api-cbme.inc.php → www-root/api/courses/cbme/cbme.php [Elentra ME v28.0-rc.43]

  19. www-root/core/modules/admin/courses/cbme/api-curriculumtags.inc.php → www-root/api/courses/cbme/curriculumtags.php [Elentra ME v28.0-rc.43]

Remove functions.inc.php methods

Patches: Elentra ME v28.0-rc.12, Elentra ME v28.0-rc.30

List of methods removed:

  1. fetch_curriculum_objectives_children [Elentra ME v28.0-rc.30]

  2. fetch_objective_child_mapped_course [Elentra ME v28.0-rc.30]

  3. clerkship_categories_inselect [Elentra ME v28.0-rc.30]

  4. clerkship_generate_included_categories [Elentra ME v28.0-rc.30]

  5. clerkship_categories_inarray [Elentra ME v28.0-rc.30]

  6. clerkship_categories_name [Elentra ME v28.0-rc.30]

  7. permissions_fetch [Elentra ME v28.0-rc.30]

  8. permissions_by_module [Elentra ME v28.0-rc.30]

  9. count_notice_reads [Elentra ME v28.0-rc.30]

  10. generate_organisation_select [Elentra ME v28.0-rc.30]

  11. nl2br_replace [Elentra ME v28.0-rc.30]

  12. br2nl_replace [Elentra ME v28.0-rc.30]

  13. communities_generate_url [Elentra ME v28.0-rc.30]

  14. communities_regenerate_url [Elentra ME v28.0-rc.30]

  15. communities_module_title [Elentra ME v28.0-rc.30]

  16. communities_module_details [Elentra ME v28.0-rc.30]

  17. communities_module_deactivate [Elentra ME v28.0-rc.30]

  18. folder_select_view [Elentra ME v28.0-rc.30]

  19. communities_pages_fetch_parent_id [Elentra ME v28.0-rc.30]

  20. communities_pages_count [Elentra ME v28.0-rc.30]

  21. communities_pages_move [Elentra ME v28.0-rc.30]

  22. recursive_delete_file [Elentra ME v28.0-rc.30]

  23. fetch_mime_type [Elentra ME v28.0-rc.30]

  24. quiz_completed_attempts [Elentra ME v28.0-rc.30]

  25. quiz_count_questions [Elentra ME v28.0-rc.30]

  26. clerkship_get_agerange [Elentra ME v28.0-rc.30]

  27. courses_fetch_objectives_cperiod [Elentra ME v28.0-rc.30]

  28. course_fetch_course_audience [Elentra ME v28.0-rc.30]

  29. tracking_output_calendar_controls [Elentra ME v28.0-rc.30]

  30. isUserInEventAttandance [Elentra ME v28.0-rc.30]

  31. events_fetch_event_audience_for_user [Elentra ME v28.0-rc.30]

  32. assessment_objectives_display_leafs [Elentra ME v28.0-rc.30]

  33. assessment_objectives_bottom_leaves [Elentra ME v28.0-rc.30]

  34. assessment_objective_decendant_mapped_course [Elentra ME v28.0-rc.30]

  35. event_objectives_display_leafs [Elentra ME v28.0-rc.30]

  36. event_objectives_bottom_leaves [Elentra ME v28.0-rc.30]

  37. event_objectives_display_leaf [Elentra ME v28.0-rc.30]

  38. event_objective_decendant_mapped_course [Elentra ME v28.0-rc.30]

  39. event_objectives_in_list [Elentra ME v28.0-rc.30]

  40. events_all_active_objectives [Elentra ME v28.0-rc.30]

  41. course_objectives_multiple_select_options_checked [Elentra ME v28.0-rc.30]

  42. course_objectives_multiple_select_table [Elentra ME v28.0-rc.30]

  43. number_suffix [Elentra ME v28.0-rc.30]

  44. getDefaultEnrollment [Elentra ME v28.0-rc.30]

  45. display_default_enrollment [Elentra ME v28.0-rc.30]

  46. objectives_inlists [Elentra ME v28.0-rc.30]

  47. objectives_inlists_conf [Elentra ME v28.0-rc.30]

  48. objectives_inselect [Elentra ME v28.0-rc.30]

  49. objectives_delete [Elentra ME v28.0-rc.30]

  50. objectives_delete_for_org [Elentra ME v28.0-rc.30]

  51. objectives_intable [Elentra ME v28.0-rc.30]

  52. build_option [Elentra ME v28.0-rc.30]

  53. display_status_messages [Elentra ME v28.0-rc.30]

  54. display_mspr_details [Elentra ME v28.0-rc.30]

  55. objectives_competency_courses [Elentra ME v28.0-rc.30]

  56. objectives_output_calendar_controls [Elentra ME v28.0-rc.30]

  57. clear_success [Elentra ME v28.0-rc.30]

  58. notice_redirect [Elentra ME v28.0-rc.30]

  59. evaluations_fetch_attempts [Elentra ME v28.0-rc.30]

  60. substitute_vars [Elentra ME v28.0-rc.30]

  61. validate_in_array [Elentra ME v28.0-rc.30]

  62. validate_user_ids [Elentra ME v28.0-rc.30]

  63. validate_course_id [Elentra ME v28.0-rc.30]

  64. validate_organisation_id [Elentra ME v28.0-rc.30]

  65. allowed_tags [Elentra ME v28.0-rc.30]

  66. display_person [Elentra ME v28.0-rc.30]

  67. display_person_email [Elentra ME v28.0-rc.30]

  68. display_photo [Elentra ME v28.0-rc.30]

  69. display_photo_link [Elentra ME v28.0-rc.30]

  70. display_photo_placeholder [Elentra ME v28.0-rc.30]

  71. display_zoom_controls [Elentra ME v28.0-rc.30]

  72. validate_integer_field [Elentra ME v28.0-rc.30]

  73. groups_get_explicitly_enrolled_course_ids [Elentra ME v28.0-rc.30]

  74. groups_get_all_course_lists [Elentra ME v28.0-rc.30]

  75. filtered_words [Elentra ME v28.0-rc.30]

  76. clean_empty_values [Elentra ME v28.0-rc.30]

  77. prepare_filter_string [Elentra ME v28.0-rc.30]

  78. event_text_change [Elentra ME v28.0-rc.30]

  79. fetch_objective_parents [Elentra ME v28.0-rc.30]

  80. count_objective_child_events [Elentra ME v28.0-rc.30]

  81. count_objective_child_courses [Elentra ME v28.0-rc.30]

  82. cmp_last_first [Elentra ME v28.0-rc.30]

  83. cmp_group_name [Elentra ME v28.0-rc.30]

  84. cmp_names_ASC [Elentra ME v28.0-rc.30]

  85. cmp_names_DESC [Elentra ME v28.0-rc.30]

  86. cmp_views_ASC [Elentra ME v28.0-rc.30]

  87. cmp_views_DESC [Elentra ME v28.0-rc.30]

  88. cmp_first_view_ASC [Elentra ME v28.0-rc.30]

  89. cmp_first_view_DESC [Elentra ME v28.0-rc.30]

  90. cmp_last_view_ASC [Elentra ME v28.0-rc.30]

  91. cmp_last_view_DESC [Elentra ME v28.0-rc.30]

  92. unixStringtoDate [Elentra ME v28.0-rc.30]

  93. assessments_items_subnavigation [Elentra ME v28.0-rc.30]

  94. removeElementsByTagName [Elentra ME v28.0-rc.30]

  95. hidden_params [Elentra ME v28.0-rc.30]

  96. isUsingSecureBrowser [Elentra ME v28.0-rc.30]

Notifications

Patches: Elentra ME v28.0-rc.33

The notifications was refactored to increase readability and maintainability by removing the switch statement and replacing it with a match statement.

New Directory Tree:

↪ Notifications
  ↪ templates
    ↪ ...  
  ↪ BaseNotification.class.php
  ↪ INotificationGenerator.class.php
  ↪ Notification.class.php

Match Statement

$notification_body_generator = match($notification_user->getContentType()){
    "assessment" => new AssessmentNotification($notification_user, $params),
    "assessment_approver" => new AssessmentApproverNotification($params["proxy_id"], $params),
    "assessment_complete_now_drafted" => new AssessmentCompleteNowNotification($params["record_id"], "drafted"),
    "assessment_complete_now_submitted" => new AssessmentCompleteNowNotification($params["record_id"], "submitted"),
    "assessment_delegation" => new AssessmentDelegationNotification($params["record_id"], $params["proxy_id"], $params["subcontent_id"], $params["nuser_id"]),
    "assessment_delegation_assignment_removed" => new AssessmentDelegationAssignmentRemovedNotification($notification_user, $params["proxy_id"], $params["subcontent_id"]),
    "assessment_delegation_general" => new AssessmentDelegationGeneralNotification($params["record_id"], $params["proxy_id"], $params["subcontent_id"]),
    "assessment_delegation_required_target_assessor" => new AssessmentDelegationRequiredTargetAssessorNotification($params["record_id"], $params["proxy_id"], $params),
    "assessment_feedback" => new AssessmentFeedbackNotificaiton($params["record_id"], $params["proxy_id"]),
    "assessment_flagged_response" => new AssessmentFlaggedNotification($params["record_id"], $params["proxy_id"]),
    "assessment_expiry_warning" => new AssessmentExpiryWarningNotification($notification_user, $params["record_id"], $params["proxy_id"]),
    "assessment_general" => new AssessmentGeneralNotification($params["proxy_id"], $params["record_id"], $notification_user),
    "assessment_removal" => new AssessmentRemovalNotification($notification_user, $params["record_id"], $params["proxy_id"]),
    "assessment_submitted" => new AssessmentSubmittedNotification($params["record_id"], $params["proxy_id"], $params),
    "assessment_submitted_notify_approver" => new AssessmentSubmittedNotifyApproverNotification($params["record_id"], $params["proxy_id"]),
    "assessment_submitted_notify_learner" => new AssessmentSubmittedNotifyLearnerNotification($params["record_id"]),
    "assessment_task_deleted" => new AssessmentTaskDeletedNotification($params["record_id"], $params["subcontent_id"]),
    "assessment_task_deleted_forwarded" => new AssessmentTaskDeletedForwardedNotification($params["record_id"], $params["subcontent_id"]),
    "assessment_task_forwarded" => new AssessmentTaskForwardedNotification($notification_user, $params["proxy_id"], $params["record_id"]),
    "assessment_task_reopened" => new AssessmentTaskReopenedNotification($params["record_id"], $params["subcontent_id"]),
    "delegated_assessment_task_deleted" => new DelegatedAssessmentTaskDeletedNotification($notification_user, $params["record_id"]),
    "delegation_task_deleted" => new DelegationTaskDeletedNotification($notification_user, $params["record_id"]),
    "distribution_assessment_summary" => new DistributionAssessmentSummaryNotification($notification_user, $params["record_id"], $params["proxy_id"], $params["subcontent_id"]),
    "evaluation" => new EvaluationNotification($notification_user, $params["record_id"], $params["subcontent_id"]),
    "evaluation_overdue" => new EvaluationNotification($notification_user, $params["record_id"], $params["subcontent_id"]),
    "evaluation_request" => new EvaluationRequestNotification($notification_user, $params["proxy_id"], $params["record_id"]),
    "evaluation_threshold" => new EvaluationThresholdNotification($notification_user, $params["proxy_id"], $params["record_id"]),
    "logbook_rotation" => new LogbookRotationNotification($notification_user, $params["proxy_id"], $params["record_id"]),
    "notification-assessment-embargoed-submitted" => new NotificationAssessmentEmbargoedSubmittedNotification($params["record_id"]),
    "preceptor_access_request" => new PreceptorAccessRequestNotification($params["record_id"], $params["proxy_id"]),
    "preceptor_inactive_access_request" => new PreceptorInactiveAccessNotification($params["record_id"], $params["proxy_id"]),
    "reminder_distribution_assessment_summary" => new DistributionAssessmentSummaryNotification($notification_user, $params["record_id"], $params["proxy_id"], $params["subcontent_id"]),
    "submit_assessment_on_behalf" => new SubmitAssessmentOnBehalfNotification($notification_user, $params["record_id"], $params["proxy_id"]),
    default => $notification_body_generator = new DefaultNotification($notification_user, $params["record_id"], $params["proxy_id"], $params["nuser_id"])
};

Last updated