diff --git a/lang/en/moodle.php b/lang/en/moodle.php
index 50ea6d2aed1..497fdb0cc3f 100644
--- a/lang/en/moodle.php
+++ b/lang/en/moodle.php
@@ -225,6 +225,9 @@ $string['bycourseorder'] = 'By course order';
$string['byname'] = 'by {$a}';
$string['bypassed'] = 'Bypassed';
$string['cachecontrols'] = 'Cache controls';
+$string['calltofeedback'] = 'Moodle HQ would like your feedback on the Moodle LMS.';
+$string['calltofeedback_give'] = 'Give feedback';
+$string['calltofeedback_remind'] = 'Remind me later';
$string['cancel'] = 'Cancel';
$string['cancelled'] = 'Cancelled';
$string['categories'] = 'Course categories';
diff --git a/lib/classes/notification.php b/lib/classes/notification.php
index 3c8783682f4..59fceeada5b 100644
--- a/lib/classes/notification.php
+++ b/lib/classes/notification.php
@@ -96,6 +96,51 @@ class notification {
);
}
+ /**
+ * @param string[] $icon The icon to use. Required keys are 'pix' and 'component'.
+ * @param string $message The message to display.
+ * @param array $actions An array of action links
+ * @param string $region Optional region name
+ * @throws \coding_exception
+ */
+ public static function add_call_to_action(array $icon, string $message, array $actions, string $region = ''): void {
+ global $OUTPUT, $PAGE;
+
+ $context = new stdClass();
+ $context->icon = $icon;
+ $context->message = $message;
+ $context->region = $region;
+
+ $context->actions = array_map(function($action) {
+ $data = [];
+ foreach ($action['data'] as $name => $value) {
+ $data[] = ['name' => $name, 'value' => $value];
+ }
+ $action['data'] = $data;
+
+ return $action;
+ }, $actions);
+
+ $notification = $OUTPUT->render_from_template('core/local/notification/cta', $context);
+
+ if ($PAGE && $PAGE->state === \moodle_page::STATE_IN_BODY) {
+ $id = uniqid();
+ echo \html_writer::span($notification, '', ['id' => $id]);
+ echo \html_writer::script(
+ "(function() {" .
+ "var notificationHolder = document.getElementById('user-notifications');" .
+ "if (!notificationHolder) { return; }" .
+ "var thisNotification = document.getElementById('{$id}');" .
+ "if (!thisNotification) { return; }" .
+ "notificationHolder.insertBefore(thisNotification.firstChild, notificationHolder.firstChild);" .
+ "thisNotification.remove();" .
+ "})();"
+ );
+ } else {
+ throw new \coding_exception('You are calling add_call_to_action() either too early or too late.');
+ }
+ }
+
/**
* Fetch all of the notifications in the stack and clear the stack.
*
diff --git a/lib/templates/local/notification/cta.mustache b/lib/templates/local/notification/cta.mustache
new file mode 100644
index 00000000000..1af3c1c3c9f
--- /dev/null
+++ b/lib/templates/local/notification/cta.mustache
@@ -0,0 +1,68 @@
+{{!
+ 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