mirror of
https://github.com/moodle/moodle.git
synced 2025-03-14 12:40:01 +01:00
Merge branch 'MDL-70083-master' of git://github.com/jleyva/moodle
This commit is contained in:
commit
46d744c34a
154
course/classes/task/content_notification_task.php
Normal file
154
course/classes/task/content_notification_task.php
Normal 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");
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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');
|
||||
}
|
||||
|
||||
/**
|
||||
|
120
course/tests/notifications_test.php
Normal file
120
course/tests/notifications_test.php
Normal 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);
|
||||
}
|
||||
}
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user