diff --git a/.upgradenotes/MDL-84733-2025031414225824.yml b/.upgradenotes/MDL-84733-2025031414225824.yml
new file mode 100644
index 00000000000..e1139e1076e
--- /dev/null
+++ b/.upgradenotes/MDL-84733-2025031414225824.yml
@@ -0,0 +1,18 @@
+issueNumber: MDL-84733
+notes:
+ mod_assign:
+ - message: >-
+ When the assignment activity sends notifications, it now sets more
+ information in the $a object passed to the languages strings for the
+ message subject and body. This is to give more flexibility to people
+ using Language Customisation. The avaiable information is the same as
+ the template context for the two templates in the next paragraph, but
+ without the messagetext/html.
+
+ Also, the overall structure of these messages is now rendered using
+ templates mod_assign/messages/notification_html and
+ mod_assign/messages/notification_html, so they can be overridden by themes.
+ As a result of this, the methods format_notification_message_text and
+ format_notification_message_html (which should have been private to
+ mod_assign and not used anywhere else) have been removed.
+ type: improved
diff --git a/message/tests/behat/message_manage_notification_preferences.feature b/message/tests/behat/message_manage_notification_preferences.feature
index 54107c84b9d..c833249a305 100644
--- a/message/tests/behat/message_manage_notification_preferences.feature
+++ b/message/tests/behat/message_manage_notification_preferences.feature
@@ -143,14 +143,14 @@ Feature: Manage notification preferences - Email
# Open the popover.
And I open the notification popover
# Confirm the submission notification is NOT visible.
- And I should not see "You have submitted your assignment submission for Test assignment name" in the "#nav-notification-popover-container" "css_element"
+ And I should not see "Assignment Submission Confirmation" in the "#nav-notification-popover-container" "css_element"
And I log in as "student2"
# Confirm the popover is showing the unread notifications.
Then I should see "1" in the "#nav-notification-popover-container [data-region='count-container']" "css_element"
# Open the popover.
And I open the notification popover
# Confirm the submission notification is visible.
- And I should see "Assignment Submission Confirmation - Test assignment name" in the "#nav-notification-popover-container" "css_element"
+ And I should see "Assignment Submission Confirmation" in the "#nav-notification-popover-container" "css_element"
Scenario: User cannot disable forced notification preferences
Given the following "courses" exist:
@@ -182,7 +182,7 @@ Feature: Manage notification preferences - Email
# Open the popover.
And I open the notification popover
# Confirm the submission notification is visible.
- And I should see "Assignment Submission Confirmation - Test assignment name" in the "#nav-notification-popover-container" "css_element"
+ And I should see "Assignment Submission Confirmation" in the "#nav-notification-popover-container" "css_element"
Scenario: User cannot disable disallowed notification preferences
Given the following "courses" exist:
@@ -214,7 +214,7 @@ Feature: Manage notification preferences - Email
# Open the popover.
And I open the notification popover
# Confirm the submission notification is NOT visible.
- And I should not see "Assignment Submission Confirmation - Test assignment name" in the "#nav-notification-popover-container" "css_element"
+ And I should not see "Assignment Submission Confirmation" in the "#nav-notification-popover-container" "css_element"
Scenario: Toggle notification preferences hides/displays options
Given I log in as "admin"
diff --git a/mod/assign/lang/en/assign.php b/mod/assign/lang/en/assign.php
index 388f0efa21e..f42554e10cf 100644
--- a/mod/assign/lang/en/assign.php
+++ b/mod/assign/lang/en/assign.php
@@ -92,8 +92,7 @@ assignment submission for \'{$a->assignment}\'
You can see it appended to your assignment submission:
{$a->url}';
-$string['assignmentmailhtml'] = '
{$a->grader} has posted some feedback on your
-assignment submission for \'{$a->assignment}\'.
+$string['assignmentmailhtml'] = '{$a->grader} has posted some feedback on your assignment submission for \'{$a->assignment}\'.
You can see it appended to your assignment submission.
';
$string['assignmentmailsmall'] = '{$a->grader} has posted some feedback on your
assignment submission for \'{$a->assignment}\' You can see it appended to your submission';
@@ -268,8 +267,7 @@ for \'{$a->assignment}\' at {$a->timeupdated}
It is available here:
{$a->url}';
-$string['gradersubmissionupdatedhtml'] = '{$a->username} has updated their assignment submission
-for \'{$a->assignment}\' at {$a->timeupdated}
+$string['gradersubmissionupdatedhtml'] = '{$a->username} has updated their assignment submission for \'{$a->assignment}\' at {$a->timeupdated}
It is available on the web site.';
$string['gradersubmissionupdatedsmall'] = '{$a->username} has updated their submission for assignment {$a->assignment}.';
$string['gradeuser'] = 'Grade {$a}';
@@ -286,8 +284,7 @@ assignment submission for \'{$a->assignment}\'
You can see it appended to your assignment submission:
{$a->url}';
-$string['feedbackavailablehtml'] = '{$a->username} has posted some feedback on your
-assignment submission for \'{$a->assignment}\'
+$string['feedbackavailablehtml'] = '{$a->username} has posted some feedback on your assignment submission for \'{$a->assignment}\'
You can see it appended to your assignment submission.';
$string['feedbackavailablesmall'] = '{$a->username} has given feedback for assignment {$a->assignment}';
$string['feedbackavailableanontext'] = 'You have new feedback on your
@@ -296,8 +293,7 @@ assignment submission for \'{$a->assignment}\'
You can see it appended to your assignment submission:
{$a->url}';
-$string['feedbackavailableanonhtml'] = 'You have new feedback on your
-assignment submission for \'{$a->assignment}\'
+$string['feedbackavailableanonhtml'] = 'You have new feedback on your assignment submission for \'{$a->assignment}\'
You can see it appended to your assignment submission.';
$string['feedbackavailableanonsmall'] = 'New feedback for assignment {$a->assignment}';
$string['feedbackplugins'] = 'Feedback plugins';
@@ -580,8 +576,7 @@ $string['submissioncopiedtext'] = 'You have made a copy of your previous assignm
You can see the status of your assignment submission:
{$a->url}';
-$string['submissioncopiedhtml'] = 'You have made a copy of your previous
-assignment submission for \'{$a->assignment}\'.
+$string['submissioncopiedhtml'] = 'You have made a copy of your previous assignment submission for \'{$a->assignment}\'.
You can see the status of your assignment submission.
';
$string['submissioncopiedsmall'] = 'You have copied your previous assignment submission for {$a->assignment}';
$string['submissiondrafts'] = 'Require students to click the submit button';
@@ -603,8 +598,7 @@ $string['submissionreceiptothertext'] = 'Your assignment submission for \'{$a->a
You can see the status of your assignment submission:
{$a->url}';
-$string['submissionreceiptotherhtml'] = 'Your assignment submission for
-\'{$a->assignment}\' has been submitted.
+$string['submissionreceiptotherhtml'] = 'Your assignment submission for \'{$a->assignment}\' has been submitted.
You can see the status of your assignment submission.';
$string['submissionreceiptothersmall'] = 'Your assignment submission for {$a->assignment} has been submitted.';
$string['submissionreceipts'] = 'Send submission receipts';
diff --git a/mod/assign/locallib.php b/mod/assign/locallib.php
index 2bc447420f4..2897b528868 100644
--- a/mod/assign/locallib.php
+++ b/mod/assign/locallib.php
@@ -6433,70 +6433,6 @@ class assign {
return $notifiableusers;
}
- /**
- * Format a notification for plain text.
- *
- * @param string $messagetype
- * @param stdClass $info
- * @param stdClass $course
- * @param stdClass $context
- * @param string $modulename
- * @param string $assignmentname
- */
- protected static function format_notification_message_text($messagetype,
- $info,
- $course,
- $context,
- $modulename,
- $assignmentname) {
- $formatparams = array('context' => $context->get_course_context());
- $posttext = format_string($course->shortname, true, $formatparams) .
- ' -> ' .
- $modulename .
- ' -> ' .
- format_string($assignmentname, true, $formatparams) . "\n";
- $posttext .= '---------------------------------------------------------------------' . "\n";
- $posttext .= get_string($messagetype . 'text', 'assign', $info)."\n";
- $posttext .= "\n---------------------------------------------------------------------\n";
- return $posttext;
- }
-
- /**
- * Format a notification for HTML.
- *
- * @param string $messagetype
- * @param stdClass $info
- * @param stdClass $course
- * @param stdClass $context
- * @param string $modulename
- * @param stdClass $coursemodule
- * @param string $assignmentname
- */
- protected static function format_notification_message_html($messagetype,
- $info,
- $course,
- $context,
- $modulename,
- $coursemodule,
- $assignmentname) {
- global $CFG;
- $formatparams = array('context' => $context->get_course_context());
- $posthtml = '' .
- '' .
- format_string($course->shortname, true, $formatparams) .
- ' ->' .
- '' .
- $modulename .
- ' ->' .
- '' .
- format_string($assignmentname, true, $formatparams) .
- '
';
- $posthtml .= '
';
- $posthtml .= '' . get_string($messagetype . 'html', 'assign', $info) . '
';
- $posthtml .= '
';
- return $posthtml;
- }
-
/**
* Message someone about something (static so it can be called from cron).
*
@@ -6506,9 +6442,9 @@ class assign {
* @param string $eventtype
* @param int $updatetime
* @param stdClass $coursemodule
- * @param stdClass $context
+ * @param context $context
* @param stdClass $course
- * @param string $modulename
+ * @param string $modulename - no longer used.
* @param string $assignmentname
* @param bool $blindmarking
* @param int $uniqueidforuser
@@ -6530,6 +6466,11 @@ class assign {
$extrainfo = []) {
global $CFG, $PAGE;
+ // We put more data into info that is used by the default language strings,
+ // so that there is more flexibility for organisations that want to use
+ // Language Customisation to customise the messages.
+
+ // Information about the sender - normally the user who performed the action.
$info = new stdClass();
if ($blindmarking) {
$userfrom = clone($userfrom);
@@ -6538,31 +6479,59 @@ class assign {
$userfrom->lastname = $uniqueidforuser;
$userfrom->email = $CFG->noreplyaddress;
} else {
- $info->username = fullname($userfrom, true);
+ $info->username = core_user::get_fullname($userfrom, $context, ['override' => true]);
}
- $info->assignment = format_string($assignmentname, true, array('context'=>$context));
- $info->url = $CFG->wwwroot.'/mod/assign/view.php?id='.$coursemodule->id;
+
+ // Information about the recipient (for greeting, etc.).
+ $info->recipentname = core_user::get_fullname($userto, $context, ['override' => true]);
+
+ // Information about the assignment.
+ $info->assignment = format_string($assignmentname, true, ['context' => $context]);
+ $info->url = $CFG->wwwroot . '/mod/assign/view.php?id=' . $coursemodule->id;
+ // Note: URLs here avoid the & character to avoid escaping issues between text and HTML messages.
+ $info->assignmentlink = '' . s($info->assignment) . ' report';
+ $info->assigncmid = $coursemodule->id;
+
+ // Information about the course.
+ $info->courseshortname = format_string($course->shortname, true, ['context' => $context]);
+ $info->coursefullname = format_string($course->fullname, true, ['context' => $context]);
+ // Note: URLs here avoid the & character to avoid escaping issues between text and HTML messages.
+ $info->courseurl = $CFG->wwwroot . '/course/view.php?id=' . $course->id;
+ $info->courseassignsurl = $CFG->wwwroot . '/mod/assign/index.php?id=' . $course->id;
+
+ // Time of the action.
$info->timeupdated = userdate($updatetime, get_string('strftimerecentfull'));
+
+ // Other data passed in.
$info = (object) array_merge((array) $info, $extrainfo);
- $postsubject = get_string($messagetype . 'small', 'assign', $info);
- $posttext = self::format_notification_message_text($messagetype,
- $info,
- $course,
- $context,
- $modulename,
- $assignmentname);
+ // Since format_string returns text with HTML characters escaped,
+ // we need to un-escape before including in the plain text email and subject line.
+ // (Test with a course or assignment called "Escaping & unescaping" to understand).
+ $plaintextinfo = clone $info;
+ $plaintextinfo->assignment = html_entity_decode($info->assignment);
+ $plaintextinfo->courseshortname = html_entity_decode($info->courseshortname);
+ $plaintextinfo->coursefullname = html_entity_decode($info->coursefullname);
+
+ // Prepare the message subject and bodies.
+ $postsubject = get_string($messagetype . 'small', 'assign', $plaintextinfo);
+
+ $renderer = $PAGE->get_renderer('mod_assign');
+ $context = clone $plaintextinfo;
+ $context->messagetext = get_string($messagetype . 'text', 'assign', $plaintextinfo);
+ // Mustache strips off all training whitespace, but we want a newline at the end.
+ $posttext = $renderer->render_from_template(
+ 'mod_assign/messages/notification_text', $context) . "\n";
+
$posthtml = '';
if ($userto->mailformat == 1) {
- $posthtml = self::format_notification_message_html($messagetype,
- $info,
- $course,
- $context,
- $modulename,
- $coursemodule,
- $assignmentname);
+ $context = clone $info;
+ $context->messagehtml = get_string($messagetype . 'html', 'assign', $info);
+ $posthtml = $renderer->render_from_template(
+ 'mod_assign/messages/notification_html', $context);
}
+ // Build the message object.
$eventdata = new \core\message\message();
$eventdata->courseid = $course->id;
$eventdata->modulename = 'assign';
diff --git a/mod/assign/templates/messages/notification_html.mustache b/mod/assign/templates/messages/notification_html.mustache
new file mode 100644
index 00000000000..07c1b87b701
--- /dev/null
+++ b/mod/assign/templates/messages/notification_html.mustache
@@ -0,0 +1,63 @@
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see .
+}}
+{{!
+ @template mod_assign/messages/notification_html
+
+ This template is used for the HTML version of messages sent from the assignment module.
+
+ Note: much more is passed to this template than might be needed, but this is to give
+ flexibility to themes.
+
+ Classes required for JS:
+ * none
+
+ Context variables required for this template:
+ * messagehtml - the main content of the message. Normally comes from a lang string.
+ * username - full name of the user who performed the action leading to the message, but will be changed to 'Participant 123' if blink marking is in use.
+ * recipentname - full name of the user the email is being sent to. (Never anonymised.)
+ * assignment - The name of this assignment.
+ * url - The URL of this assignment activity.
+ * assigncmid - Course-module id of this assignment.
+ * courseshortname - short name of the course this assignment is in.
+ * coursefullname - full name of the course this assignment is in.
+ * courseurl - URL of the course page.
+ * courseassignsurl - URL of the paeg listing all assignments in the course.
+ * timeupdated - Time the action that triggered this message was performed, formatted for display.
+
+ Example context (json):
+ {
+ "messagehtml": "Your assignment for Assignment 1 has been submitted. You can check its status on the assignment page.
",
+ "username": "Trusty Teacher",
+ "recipientname": "Smart Student",
+ "assignment": "Assignment 1",
+ "url": "https://qa.moodledemo.net/mod/assign/view.php?id=5",
+ "assigncmid": 5,
+ "courseshortname": "A100",
+ "coursefullname": "Assessment for beginners",
+ "courseurl": "https://qa.moodledemo.net/course/view.php?id=2",
+ "courseassignsurl": "https://qa.moodledemo.net/mod/assign/index.php?id=2",
+ "timeupdated": "Fri, 14 Mar 2025, 11:08 PM"
+ }
+}}
+
+ {{{courseshortname}}} ->
+ {{#str}}modulename, mod_assign{{/str}} ->
+ {{{assignment}}}
+
+
+{{{messagehtml}}}
+
diff --git a/mod/assign/templates/messages/notification_text.mustache b/mod/assign/templates/messages/notification_text.mustache
new file mode 100644
index 00000000000..7538f0fb10d
--- /dev/null
+++ b/mod/assign/templates/messages/notification_text.mustache
@@ -0,0 +1,60 @@
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see .
+}}
+{{!
+ @template mod_assign/messages/notification_text
+
+ This template is used for the plain text version of messages sent from the assignment module.
+
+ Note: much more is passed to this template than might be needed, but this is to give
+ flexibility to themes.
+
+ Classes required for JS:
+ * none
+
+ Context variables required for this template:
+ * messagetext - the main content of the message. Normally comes from a lang string.
+ * username - full name of the user who performed the action leading to the message, but will be changed to 'Participant 123' if blink marking is in use.
+ * recipentname - full name of the user the email is being sent to. (Never anonymised.)
+ * assignment - The name of this assignment.
+ * url - The URL of this assignment activity.
+ * assigncmid - Course-module id of this assignment.
+ * courseshortname - short name of the course this assignment is in.
+ * coursefullname - full name of the course this assignment is in.
+ * courseurl - URL of the course page.
+ * courseassignsurl - URL of the paeg listing all assignments in the course.
+ * timeupdated - Time the action that triggered this message was performed, formatted for display.
+
+ Example context (json):
+ {
+ "messagehtml": "Your assignment for Assignment 1 has been submitted. You can check its status at https://qa.moodledemo.net/mod/assign/view.php?id=5.",
+ "username": "Trusty Teacher",
+ "recipientname": "Smart Student",
+ "assignment": "Assignment 1",
+ "url": "https://qa.moodledemo.net/mod/assign/view.php?id=5",
+ "assigncmid": 5,
+ "courseshortname": "A100",
+ "coursefullname": "Assessment for beginners",
+ "courseurl": "https://qa.moodledemo.net/course/view.php?id=2",
+ "courseassignsurl": "https://qa.moodledemo.net/mod/assign/index.php?id=2",
+ "timeupdated": "Fri, 14 Mar 2025, 11:08 PM"
+ }
+}}
+{{{courseshortname}}} -> {{#str}}modulename, mod_assign{{/str}} -> {{{assignment}}}
+---------------------------------------------------------------------
+{{{messagetext}}}
+
+---------------------------------------------------------------------
diff --git a/mod/assign/tests/generator.php b/mod/assign/tests/generator.php
index f4cbcef10a6..59ffc3b0321 100644
--- a/mod/assign/tests/generator.php
+++ b/mod/assign/tests/generator.php
@@ -41,7 +41,7 @@ trait mod_assign_test_generator {
* Convenience function to create a testable instance of an assignment.
*
* @param array $params Array of parameters to pass to the generator
- * @return testable_assign Testable wrapper around the assign class.
+ * @return mod_assign_testable_assign Testable wrapper around the assign class.
*/
protected function create_instance($course, $params = [], $options = []) {
$params['course'] = $course->id;
diff --git a/mod/assign/tests/locallib_test.php b/mod/assign/tests/locallib_test.php
index 5ca19a3933e..9b850bbbdf9 100644
--- a/mod/assign/tests/locallib_test.php
+++ b/mod/assign/tests/locallib_test.php
@@ -1724,13 +1724,14 @@ final class locallib_test extends \advanced_testcase {
\core\cron::setup_user();
\assign::cron();
- $course = $this->getDataGenerator()->create_course();
+ $course = $this->getDataGenerator()->create_course(['shortname' => 'E&U']);
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
// Now create an assignment and add some feedback.
$this->setUser($teacher);
$assign = $this->create_instance($course, [
+ 'name' => 'Escaping & Unescaping',
'sendstudentnotifications' => 1,
'markingworkflow' => 1,
]);
@@ -1765,8 +1766,45 @@ final class locallib_test extends \advanced_testcase {
$messages = $sink->get_messages();
$this->assertEquals(1, count($messages));
- $this->assertEquals(1, $messages[0]->notification);
- $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname);
+
+ // Get some bits we will need to verify the content of the message.
+ $assignname = $assign->get_instance()->name;
+ $assignurl = (new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]))->out();
+ $teachername = fullname($teacher);
+ $assignsurl = (new \moodle_url('/mod/assign/index.php', ['id' => $course->id]))->out();
+ $courseurl = (new \moodle_url('/course/view.php', ['id' => $course->id]))->out();
+
+ $message = $messages[0];
+ $this->assertEquals(1, $message->notification);
+ $this->assertEquals(format_string($assign->get_instance()->name), $message->contexturlname);
+ $this->assertEquals("$teachername has given feedback for assignment $assignname", $message->subject, );
+ $this->assertEquals("E&U -> Assignment -> Escaping & Unescaping
+---------------------------------------------------------------------
+$teachername has posted some feedback on your
+assignment submission for 'Escaping & Unescaping'
+
+You can see it appended to your assignment submission:
+
+ $assignurl
+
+---------------------------------------------------------------------
+",
+ $message->fullmessage,
+ );
+ $this->assertEquals(
+ '
+ E&U ->
+ Assignment ->
+ Escaping & Unescaping
+
+
+' . $teachername . ' has posted some feedback on your ' .
+ 'assignment submission for \'Escaping & Unescaping\'
+You can see it appended to your assignment submission.
+
',
+ $message->fullmessagehtml
+ );
}
public function test_cron_message_includes_courseid(): void {