diff --git a/course/edit_form.php b/course/edit_form.php
index fdcdad7f299..897ee07b2c7 100644
--- a/course/edit_form.php
+++ b/course/edit_form.php
@@ -403,6 +403,16 @@ class course_edit_form extends moodleform {
$handler->set_parent_context($categorycontext); // For course handler only.
$handler->instance_form_definition($mform, empty($course->id) ? 0 : $course->id);
+ // Add communication plugins to the form.
+ if (core_communication\api::is_available()) {
+ $communication = \core_communication\api::load_by_instance(
+ 'core_course',
+ 'coursecommunication',
+ empty($course->id) ? 0 : $course->id);
+ $communication->form_definition($mform);
+ $communication->set_data($course);
+ }
+
// When two elements we need a group.
$buttonarray = array();
$classarray = array('class' => 'form-submit');
diff --git a/course/lib.php b/course/lib.php
index 7b9f0f9ec92..3748203e437 100644
--- a/course/lib.php
+++ b/course/lib.php
@@ -24,6 +24,7 @@
defined('MOODLE_INTERNAL') || die;
+use core_course\external\course_summary_exporter;
use core_courseformat\base as course_format;
require_once($CFG->libdir.'/completionlib.php');
@@ -2313,6 +2314,18 @@ function create_course($data, $editoroptions = NULL) {
core_tag_tag::set_item_tags('core', 'course', $course->id, context_course::instance($course->id), $data->tags);
}
+ // Communication api implementation in course.
+ if (isset($data->selectedcommunication) && core_communication\api::is_available()) {
+ // Prepare the communication api date.
+ $courseimage = course_summary_exporter::get_course_image($course);
+ $communicationroomname = !empty($data->communicationroomname) ? $data->communicationroomname : $data->fullname;
+ $selectedcommunication = $data->selectedcommunication;
+
+ // Communication api call.
+ $communication = \core_communication\api::load_by_instance('core_course', 'coursecommunication', $course->id);
+ $communication->create_and_configure_room($selectedcommunication, $communicationroomname, $courseimage);
+ }
+
// Save custom fields if there are any of them in the form.
$handler = core_course\customfield\course_handler::create();
// Make sure to set the handler's parent context first.
@@ -2434,6 +2447,71 @@ function update_course($data, $editoroptions = NULL) {
$data->showcompletionconditions = null;
}
+ // Check if provider is selected.
+ $provider = $data->selectedcommunication ?? null;
+ // If the course moved to hidden category, set provider to none.
+ if ($changesincoursecat && empty($data->visible)) {
+ $provider = 'none';
+ }
+
+ // Communication api call.
+ if (!empty($provider) && core_communication\api::is_available()) {
+ // Prepare the communication api data.
+ $courseimage = course_summary_exporter::get_course_image($data);
+ if (!$courseimage) {
+ $courseimage = null;
+ }
+
+ // This nasty logic is here because of hide course doesn't pass anything in the data object.
+ if (!empty($data->communicationroomname)) {
+ $communicationroomname = $data->communicationroomname;
+ } else {
+ $communicationroomname = $data->fullname ?? $oldcourse->fullname;
+ }
+
+ // Update communication room membership of enrolled users.
+ require_once($CFG->libdir . '/enrollib.php');
+ $courseusers = enrol_get_course_users($data->id);
+ $enrolledusers = [];
+
+ foreach ($courseusers as $user) {
+ $enrolledusers[] = $user->id;
+ }
+
+ $communication = \core_communication\api::load_by_instance(
+ 'core_course',
+ 'coursecommunication',
+ $data->id
+ );
+
+ $addafterupdate = false;
+ if ($provider !== $communication->get_provider()) {
+ // If provider set to none, remove all the members.
+ if ($provider === 'none') {
+ $communication->remove_members_from_room($enrolledusers);
+ } else if (
+ // If previous provider was not none and current provider is not none, but a different provider, remove members.
+ $communication->get_provider() !== '' &&
+ $communication->get_provider() !== 'none' &&
+ $provider !== $communication->get_provider()
+ ) {
+ $communication->remove_members_from_room($enrolledusers);
+ $addafterupdate = true;
+ } else if (
+ // If previous provider was none and current provider is not none, but a different provider, remove members.
+ ($communication->get_provider() === '' || $communication->get_provider() === 'none') &&
+ $provider !== $communication->get_provider()
+ ) {
+ $addafterupdate = true;
+ }
+ }
+
+ $communication->update_room($provider, $communicationroomname, $courseimage);
+ if ($addafterupdate) {
+ $communication->add_members_to_room($enrolledusers, false);
+ }
+ }
+
// Update custom fields if there are any of them in the form.
$handler = core_course\customfield\course_handler::create();
$handler->instance_form_save($data);
diff --git a/lang/en/course.php b/lang/en/course.php
index d4471fe6b29..e01ce31d124 100644
--- a/lang/en/course.php
+++ b/lang/en/course.php
@@ -51,6 +51,7 @@ $string['aria:recommendedtab'] = 'Recommended activities';
$string['aria:modulefavourite'] = 'Star {$a} activity';
$string['browsecourseadminindex'] = 'Browse the course administration with this index.';
$string['browsesettingindex'] = 'Browse settings with this index.';
+$string['communicationroomlink'] = 'Chat to course participants';
$string['completion_automatic:done'] = 'Done:';
$string['completion_automatic:failed'] = 'Failed:';
$string['completion_automatic:todo'] = 'To do:';
diff --git a/lib/classes/output/icon_system_fontawesome.php b/lib/classes/output/icon_system_fontawesome.php
index 5fc08899f8d..314f16c8189 100644
--- a/lib/classes/output/icon_system_fontawesome.php
+++ b/lib/classes/output/icon_system_fontawesome.php
@@ -414,6 +414,7 @@ class icon_system_fontawesome extends icon_system_font {
'core:t/locktime' => 'fa-lock',
'core:t/markasread' => 'fa-check',
'core:t/messages' => 'fa-comments',
+ 'core:t/messages-o' => 'fa-comments-o',
'core:t/message' => 'fa-comment-o',
'core:t/more' => 'fa-caret-down',
'core:t/move' => 'fa-arrows-v',
diff --git a/lib/moodlelib.php b/lib/moodlelib.php
index a20699e381e..d7da9862deb 100644
--- a/lib/moodlelib.php
+++ b/lib/moodlelib.php
@@ -4145,6 +4145,19 @@ function delete_user(stdClass $user) {
// Keep a copy of user context, we need it for event.
$usercontext = context_user::instance($user->id);
+ // Remove user from communication rooms immediately.
+ if (core_communication\api::is_enabled()) {
+ foreach (enrol_get_users_courses($user->id) as $course) {
+ $communication = \core_communication\communication_processor::load_by_instance(
+ 'core_course',
+ 'coursecommunication',
+ $course->id
+ );
+ $communication->get_room_user_provider()->remove_members_from_room([$user->id]);
+ $communication->delete_instance_user_mapping([$user->id]);
+ }
+ }
+
// Delete all grades - backup is kept in grade_grades_history table.
grade_user_delete($user->id);
@@ -5031,7 +5044,7 @@ function set_login_session_preferences() {
* failed, but you have no way of knowing which.
*/
function delete_course($courseorid, $showfeedback = true) {
- global $DB;
+ global $DB, $CFG;
if (is_object($courseorid)) {
$courseid = $courseorid->id;
@@ -5071,6 +5084,26 @@ function delete_course($courseorid, $showfeedback = true) {
// Delete the course and related context instance.
context_helper::delete_instance(CONTEXT_COURSE, $courseid);
+ // Communication provider delete associated information.
+ $communication = \core_communication\api::load_by_instance(
+ 'core_course',
+ 'coursecommunication',
+ $course->id
+ );
+
+ // Update communication room membership of enrolled users.
+ require_once($CFG->libdir . '/enrollib.php');
+ $courseusers = enrol_get_course_users($courseid);
+ $enrolledusers = [];
+
+ foreach ($courseusers as $user) {
+ $enrolledusers[] = $user->id;
+ }
+
+ $communication->remove_members_from_room($enrolledusers);
+
+ $communication->delete_room();
+
$DB->delete_records("course", array("id" => $courseid));
$DB->delete_records("course_format_options", array("courseid" => $courseid));
diff --git a/lib/outputrenderers.php b/lib/outputrenderers.php
index 2bcc1b27619..b98830b5d3e 100644
--- a/lib/outputrenderers.php
+++ b/lib/outputrenderers.php
@@ -4285,6 +4285,54 @@ EOD;
return !empty($this->services_support_link()) || !empty($this->page_doc_link()) || !empty($this->supportemail());
}
+ /**
+ * Helper function to decide whether to show the communication link or not.
+ *
+ * @return bool
+ */
+ public function has_communication_links(): bool {
+ if (!core_communication\api::is_available()) {
+ return false;
+ }
+ return !empty($this->communication_link());
+ }
+
+ /**
+ * Returns the communication link, complete with html.
+ *
+ * @return string
+ */
+ public function communication_link(): string {
+
+ if (during_initial_install()) {
+ return '';
+ }
+
+ $link = $this->communication_url() ?? '';
+ $commicon = $this->pix_icon('t/messages-o', '', 'moodle', ['class' => 'fa fa-comments']);
+ $newwindowicon = $this->pix_icon('i/externallink', get_string('opensinnewwindow'), 'moodle', ['class' => 'ml-1']);
+ $content = $commicon . get_string('communicationroomlink', 'course') . $newwindowicon;
+ $html = html_writer::tag('a', $content, ['target' => '_blank', 'href' => $link]);
+
+ return !empty($link) ? $html : '';
+ }
+
+ /**
+ * Returns the communication url for a given instance if it exists.
+ *
+ * @return string
+ */
+ public function communication_url(): string {
+ global $COURSE;
+ $url = '';
+ if ($COURSE->id !== SITEID) {
+ $comm = \core_communication\api::load_by_instance('core_course', 'coursecommunication', $COURSE->id);
+ $url = $comm->get_communication_room_url();
+ }
+
+ return !empty($url) ? $url : '';
+ }
+
/**
* Returns the page heading menu.
*
diff --git a/theme/boost/scss/moodle/core.scss b/theme/boost/scss/moodle/core.scss
index 11aa1366e75..581903a3a93 100644
--- a/theme/boost/scss/moodle/core.scss
+++ b/theme/boost/scss/moodle/core.scss
@@ -2309,10 +2309,22 @@ $footer-link-color: $bg-inverse-link-color !default;
@include box-shadow($popover-box-shadow);
}
+.btn-footer-communication {
+ display: none;
+ position: fixed;
+ bottom: 5rem;
+ right: 2rem;
+ @include box-shadow($popover-box-shadow);
+}
+
.hasstickyfooter .btn-footer-popover {
bottom: calc(1rem + #{$stickyfooter-height});
}
+.hasstickyfooter .btn-footer-communication {
+ bottom: calc(4rem + #{$stickyfooter-height});
+}
+
.popover.footer {
.popover-body {
padding: 0;
@@ -2339,7 +2351,8 @@ $footer-link-color: $bg-inverse-link-color !default;
.jsenabled #page-footer .footer-content-popover {
display: none;
}
- .jsenabled .btn-footer-popover {
+ .jsenabled .btn-footer-popover,
+ .jsenabled .btn-footer-communication {
display: block;
z-index: $zindex-dropdown;
}
diff --git a/theme/boost/style/moodle.css b/theme/boost/style/moodle.css
index f37ffbaca16..190c1a2e7ec 100644
--- a/theme/boost/style/moodle.css
+++ b/theme/boost/style/moodle.css
@@ -25138,10 +25138,21 @@ h3.sectionname .inplaceeditable.inplaceeditingon .editinstructions {
right: 2rem;
}
+.btn-footer-communication {
+ display: none;
+ position: fixed;
+ bottom: 5rem;
+ right: 2rem;
+}
+
.hasstickyfooter .btn-footer-popover {
bottom: calc(1rem + max(96px, 0.9375rem * 3));
}
+.hasstickyfooter .btn-footer-communication {
+ bottom: calc(4rem + max(96px, 0.9375rem * 3));
+}
+
.popover.footer .popover-body {
padding: 0;
}
@@ -25164,7 +25175,8 @@ h3.sectionname .inplaceeditable.inplaceeditingon .editinstructions {
.jsenabled #page-footer .footer-content-popover {
display: none;
}
- .jsenabled .btn-footer-popover {
+ .jsenabled .btn-footer-popover,
+ .jsenabled .btn-footer-communication {
display: block;
z-index: 1000;
}
diff --git a/theme/boost/templates/footer.mustache b/theme/boost/templates/footer.mustache
index 5afe5891f24..1bd8d6af3d4 100644
--- a/theme/boost/templates/footer.mustache
+++ b/theme/boost/templates/footer.mustache
@@ -27,18 +27,31 @@
"has_popover_links": true,
"services_support": "Services and support",
"login_info": "You are logged in as cute kitten",
- "moodle_release": "90210"
+ "moodle_release": "90210",
+ "has_communication_links": true,
+ "communication_url": "https://element:8081/#/room/#yourroom:synapse",
+ "communication_link": "Communication room"
}
}
}}