Merge branch 'MDL-70083-master' of git://github.com/jleyva/moodle

This commit is contained in:
Shamim Rezaie 2021-11-05 14:37:44 +11:00
commit 46d744c34a
8 changed files with 308 additions and 3 deletions

View File

@ -0,0 +1,154 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Class handling course content updates notifications.
*
* @package core_course
* @copyright 2021 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_course\task;
use core\task\adhoc_task;
/**
* Class handling course content updates notifications.
*
* @package core_course
* @copyright 2021 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class content_notification_task extends adhoc_task {
// Use the logging trait for better logging.
use \core\task\logging_trait;
/**
* Run the main task.
*
* @throws \coding_exception if something wrong happens.
*/
public function execute() {
global $CFG, $OUTPUT;
require_once($CFG->libdir . '/enrollib.php');
$data = $this->get_custom_data();
$course = get_course($data->courseid);
$modinfo = get_fast_modinfo($course);
$cm = $modinfo->cms[$data->cmid];
$isupdate = !empty($data->update);
if (!$course->visible || !$cm->visible) {
// The course or module is hidden. We don't check if the user can see hidden courses, does not make sense here.
// Permissions may have changed since it was queued.
return;
}
// Get only active users.
$coursecontext = \context_course::instance($course->id);
$modcontext = \context_module::instance($cm->id);
$users = get_enrolled_users($coursecontext, '', 0, 'u.*', null, 0, 0, true);
if (empty($users)) {
return;
}
$userfrom = \core_user::get_user($data->userfrom);
// Now send the messages
$countusers = count($users);
$sentcount = $errorcount = 0;
$this->log_start("Sending course content update notifications to {$countusers} potential users
from user with id {$userfrom->id}.");
foreach ($users as $user) {
cron_setup_user($user, $course);
$cm = get_fast_modinfo($course)->cms[$cm->id];
if (!$cm->uservisible && !$cm->is_visible_on_course_page()) {
// User can't access or see the activity in the course page.
$this->log("Ignoring user {$user->id} (no permissions to see the module)", 1);
continue;
}
// Get module names in the user's language.
$modnames = get_module_types_names();
$a = [
'coursename' => get_course_display_name_for_list($course),
'courselink' => (new \moodle_url('/course/view.php', ['id' => $course->id]))->out(false),
'modulename' => format_string($cm->name, $modcontext->id),
'moduletypename' => $modnames[$cm->modname],
'link' => (new \moodle_url('/mod/' . $cm->modname . '/view.php', ['id' => $cm->id]))->out(false),
'notificationpreferenceslink' =>
(new \moodle_url('/message/notificationpreferences.php', ['userid' => $user->id]))->out(false),
];
if ($isupdate) {
$messagesubject = get_string('coursecontentnotifupdate', 'course', $a);
$messagebody = get_string('coursecontentnotifupdatebody', 'course', $a);
} else {
$messagesubject = get_string('coursecontentnotifnew', 'course', $a);
$messagebody = get_string('coursecontentnotifnewbody', 'course', $a);
}
// Send notification.
$eventdata = new \core\message\message();
$eventdata->courseid = $course->id;
$eventdata->component = 'moodle';
$eventdata->name = 'coursecontentupdated';
$eventdata->userfrom = $userfrom;
$eventdata->userto = $user;
$eventdata->subject = $messagesubject;
$eventdata->fullmessageformat = FORMAT_HTML;
$eventdata->fullmessagehtml = $messagebody;
$eventdata->smallmessage = strip_tags($eventdata->fullmessagehtml);
$eventdata->contexturl = (new \moodle_url('/mod/' . $cm->modname . '/view.php', ['id' => $cm->id]))->out(false);
$eventdata->contexturlname = $cm->name;
$eventdata->notification = 1;
$eventdata->customdata = [
'notificationiconurl' => $cm->get_icon_url()->out(false),
];
if ($courseimage = \core_course\external\course_summary_exporter::get_course_image($course)) {
$eventdata->customdata['notificationpictureurl'] = $courseimage;
}
$completion = \core_completion\cm_completion_details::get_instance($cm, $user->id);
$activitydates = \core\activity_dates::get_dates_for_module($cm, $user->id);
if (!empty($activitydates)) {
$activityinfo = new \core_course\output\activity_information($cm, $completion, $activitydates);
$data = $activityinfo->export_for_template($OUTPUT);
foreach ($data->activitydates as $date) {
$eventdata->fullmessagehtml .= \html_writer::div($date['label'] . ' ' . $date['datestring']);
}
}
$eventdata->fullmessage = html_to_text($eventdata->fullmessagehtml);
if (message_send($eventdata)) {
$this->log("Notification sent to user with id {$user->id}", 1);
$sentcount++;
} else {
$this->log("Failed to send notification to user with id {$user->id}", 1);
$errorcount++;
}
}
$this->log_finish("Sent {$sentcount} notifications with {$errorcount} failures");
}
}

View File

@ -213,7 +213,7 @@ function plugin_extend_coursemodule_edit_post_actions($moduleinfo, $course) {
* @return object moduleinfo update with grading management info
*/
function edit_module_post_actions($moduleinfo, $course) {
global $CFG;
global $CFG, $USER;
require_once($CFG->libdir.'/gradelib.php');
$modcontext = context_module::instance($moduleinfo->coursemodule);
@ -386,6 +386,18 @@ function edit_module_post_actions($moduleinfo, $course) {
// Allow plugins to extend the course module form.
$moduleinfo = plugin_extend_coursemodule_edit_post_actions($moduleinfo, $course);
if (!empty($moduleinfo->coursecontentnotification)) {
// Schedule adhoc-task for delivering the course content updated notification.
if ($course->visible && $moduleinfo->visible) {
$adhocktask = new \core_course\task\content_notification_task();
$adhocktask->set_custom_data(
['update' => $moduleinfo->update, 'cmid' => $moduleinfo->coursemodule,
'courseid' => $course->id, 'userfrom' => $USER->id]);
$adhocktask->set_component('course');
\core\task\manager::queue_adhoc_task($adhocktask, true);
}
}
return $moduleinfo;
}

View File

@ -1190,6 +1190,10 @@ abstract class moodleform_mod extends moodleform {
$mform = $this->_form;
$mform->addElement('checkbox', 'coursecontentnotification', get_string('coursecontentnotification', 'course'));
$mform->addHelpButton('coursecontentnotification', 'coursecontentnotification', 'course');
$mform->closeHeaderBefore('coursecontentnotification');
// elements in a row need a group
$buttonarray = array();
@ -1209,7 +1213,6 @@ abstract class moodleform_mod extends moodleform {
$mform->addGroup($buttonarray, 'buttonar', '', array(' '), false);
$mform->setType('buttonar', PARAM_RAW);
$mform->closeHeaderBefore('buttonar');
}
/**

View File

@ -0,0 +1,120 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
namespace tests\core_course;
/**
* Contains tests for course related notifications.
*
* @package core
* @subpackage course
* @copyright 2021 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class notifications_test extends \advanced_testcase {
/**
* Test coursecontentupdated class.
*/
public function test_coursecontentupdated_notifications() {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
$course = self::getDataGenerator()->create_course();
// Enrol couple of students to receive a notification and one unactive enrolment.
$user1 = self::getDataGenerator()->create_user();
$user2 = self::getDataGenerator()->create_user();
$user3 = self::getDataGenerator()->create_user();
self::getDataGenerator()->enrol_user($user1->id, $course->id, 'student');
self::getDataGenerator()->enrol_user($user2->id, $course->id, 'student');
self::getDataGenerator()->enrol_user($user3->id, $course->id, 'student', 'manual', time() - YEARSECS, time() - WEEKSECS);
$url = self::getDataGenerator()->create_module('url', ['course' => $course]);
// Test update.
$moduleinfo = $DB->get_record('course_modules', array('id' => $url->cmid));
$moduleinfo->modulename = 'url';
$moduleinfo->coursemodule = $url->cmid;
$moduleinfo->display = 1;
$moduleinfo->externalurl = '';
$moduleinfo->update = 1;
$draftid = 0;
file_prepare_draft_area($draftid, \context_module::instance($url->cmid)->id, 'mod_url', 'intro', 0);
$moduleinfo->introeditor = [
'itemid' => $draftid,
'text' => '<p>Yo</p>',
'format' => FORMAT_HTML
];
$modurl = (new \moodle_url('/mod/url/view.php', ['id' => $url->cmid]))->out(false);
// Check course content changed notifications.
$moduleinfo->coursecontentnotification = 1;
// Create the module.
update_module(clone $moduleinfo); // We use clone to keep the original object untouch for later use.
// Redirect messages to sink and stop buffer output from CLI task.
$sink = $this->redirectMessages();
ob_start();
$this->runAdhocTasks('\core_course\task\content_notification_task');
$output = ob_get_contents();
ob_end_clean();
$messages = $sink->get_messages();
$sink->close();
// We have 3 students, one with a non-active enrolment that should not receive a notification.
$this->assertCount(2, $messages);
foreach ($messages as $message) {
$this->assertEquals('coursecontentupdated', $message->eventtype);
$this->assertEquals($modurl, $message->contexturl);
}
// Now, set the course to not visible.
$DB->set_field('course', 'visible', 0, ['id' => $course->id]);
$this->setAdminUser();
update_module(clone $moduleinfo);
// Redirect messages to sink and stop buffer output from CLI task.
$sink = $this->redirectMessages();
ob_start();
$this->runAdhocTasks('\core_course\task\content_notification_task');
$output = ob_get_contents();
ob_end_clean();
$messages = $sink->get_messages();
$sink->close();
// No messages, course not visible.
$this->assertCount(0, $messages);
// Now, set the module to not visible.
$DB->set_field('course', 'visible', 1, ['id' => $course->id]);
$this->setAdminUser();
$moduleinfo->visible = 0;
update_module(clone $moduleinfo);
// Redirect messages to sink and stop buffer output from CLI task.
$sink = $this->redirectMessages();
ob_start();
$this->runAdhocTasks('\core_course\task\content_notification_task');
$output = ob_get_contents();
ob_end_clean();
$messages = $sink->get_messages();
$sink->close();
// No messages, module not visible.
$this->assertCount(0, $messages);
}
}

View File

@ -59,6 +59,12 @@ $string['completion_setby:manual:done'] = '{$a->activityname} is marked by {$a->
$string['completion_setby:manual:markdone'] = '{$a->activityname} is marked by {$a->setby} as not done. Press to mark as done.';
$string['completionrequirements'] = 'Completion requirements for {$a}';
$string['coursealreadyfinished'] = 'Course already finished';
$string['coursecontentnotification'] = 'Send content change notification';
$string['coursecontentnotifnew'] = '{$a->coursename} new content';
$string['coursecontentnotifnewbody'] = '<p>{$a->moduletypename} <a href="{$a->link}">{$a->modulename}</a> is new in the course <a href="{$a->courselink}">{$a->coursename}</a>.</p><p><a href="{$a->notificationpreferenceslink}">Change your notification preferences</a></p>';
$string['coursecontentnotifupdate'] = '{$a->coursename} content change';
$string['coursecontentnotifupdatebody'] = '<p>{$a->moduletypename} <a href="{$a->link}">{$a->modulename}</a> has been changed in the course <a href="{$a->courselink}">{$a->coursename}</a>.</p><p><a href="{$a->notificationpreferenceslink}">Change your notification preferences</a></p>';
$string['coursecontentnotification_help'] = 'You can send a notification to the course users about any new or updated resource or activity. Please notice that the notification will be sent only if the new resource or activity is visible.';
$string['coursenotyetstarted'] = 'The course has not yet started';
$string['coursenotyetfinished'] = 'The course has not yet finished';
$string['coursetoolong'] = 'The course is too long';

View File

@ -1256,6 +1256,7 @@ $string['messageprovider:badgerecipientnotice'] = 'Badge recipient notifications
$string['messageprovider:competencyplancomment'] = 'Comment posted on a learning plan';
$string['messageprovider:competencyusercompcomment'] = 'Comment posted on a competency';
$string['messageprovider:coursecompleted'] = 'Course completed';
$string['messageprovider:coursecontentupdated'] = 'Course content changes';
$string['messageprovider:courserequestapproved'] = 'Course creation request approval notification';
$string['messageprovider:courserequested'] = 'Course creation request notification';
$string['messageprovider:courserequestrejected'] = 'Course creation request rejection notification';

View File

@ -89,6 +89,15 @@ $messageproviders = array (
// Course completed. Requires course completion configured at course level. It does not work with just activity progress.
'coursecompleted' => [],
// Course content updated. New content (activities or resources) has been created or existing content updated.
'coursecontentupdated' => array (
'defaults' => array(
'popup' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_LOGGEDIN + MESSAGE_DEFAULT_LOGGEDOFF,
'email' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_LOGGEDOFF,
'airnotifier' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_LOGGEDIN + MESSAGE_DEFAULT_LOGGEDOFF,
),
),
// Badge award notification to a badge recipient.
'badgerecipientnotice' => array (
'defaults' => array(

View File

@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
$version = 2021110200.00; // YYYYMMDD = weekly release date of this DEV branch.
$version = 2021110200.01; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.
$release = '4.0dev+ (Build: 20211102)'; // Human-friendly version name