Commit 02a73d76 authored by Juan Leyva's avatar Juan Leyva
Browse files

MDL-56307 course: New course_check_module_updates_since method

parent 1896b800
......@@ -3601,3 +3601,172 @@ function course_validate_dates($coursedata) {
return false;
}
/**
* Check for course updates in the given context level instances (only modules supported right Now)
*
* @param stdClass $course course object
* @param array $tocheck instances to check for updates
* @param array $filter check only for updates in these areas
* @return array list of warnings and instances with updates information
* @since Moodle 3.2
*/
function course_check_updates($course, $tocheck, $filter = array()) {
global $CFG, $DB;
$instances = array();
$warnings = array();
$modulescallbacksupport = array();
$modinfo = get_fast_modinfo($course);
$supportedplugins = get_plugin_list_with_function('mod', 'check_updates_since');
// Check instances.
foreach ($tocheck as $instance) {
if ($instance['contextlevel'] == 'module') {
// Check module visibility.
$modinfoexception = false;
try {
$cm = $modinfo->get_cm($instance['id']);
} catch (Exception $e) {
$modinfoexception = true;
}
if ($modinfoexception or !$cm->uservisible) {
$warnings[] = array(
'item' => 'module',
'itemid' => $instance['id'],
'warningcode' => 'nonuservisible',
'message' => 'You don\'t have access to this module.'
);
continue;
}
if (empty($supportedplugins['mod_' . $cm->modname])) {
$warnings[] = array(
'item' => 'module',
'itemid' => $instance['id'],
'warningcode' => 'missingcallback',
'message' => 'This module does not implement the check_updates_since callback: ' . $instance['contextlevel'],
);
continue;
}
// Retrieve the module instance.
$instances[] = array(
'contextlevel' => $instance['contextlevel'],
'id' => $instance['id'],
'updates' => call_user_func($cm->modname . '_check_updates_since', $cm, $instance['since'], $filter)
);
} else {
$warnings[] = array(
'item' => 'contextlevel',
'itemid' => $instance['id'],
'warningcode' => 'contextlevelnotsupported',
'message' => 'Context level not yet supported ' . $instance['contextlevel'],
);
}
}
return array($instances, $warnings);
}
/**
* Check module updates since a given time.
* This function checks for updates in the module config, file areas, completion, grades, comments and ratings.
*
* @param cm_info $cm course module data
* @param int $from the time to check
* @param array $fileareas additional file ares to check
* @param array $filter if we need to filter and return only selected updates
* @return stdClass object with the different updates
* @since Moodle 3.2
*/
function course_check_module_updates_since($cm, $from, $fileareas = array(), $filter = array()) {
global $DB, $CFG, $USER;
$context = $cm->context;
$mod = $DB->get_record($cm->modname, array('id' => $cm->instance), '*', MUST_EXIST);
$updates = new stdClass();
$course = get_course($cm->course);
$component = 'mod_' . $cm->modname;
// Check changes in the module configuration.
if (isset($mod->timemodified) and (empty($filter) or in_array('configuration', $filter))) {
$updates->configuration = $mod->timemodified > $from;
}
// Check for updates in files.
if (plugin_supports('mod', $cm->modname, FEATURE_MOD_INTRO)) {
$fileareas[] = 'intro';
}
if (!empty($fileareas) and (empty($filter) or in_array('fileareas', $filter))) {
$fs = get_file_storage();
$extrasql = 'AND (f.timecreated > :since1 OR f.timemodified > :since2)';
$extraconditions = array('since1' => $from, 'since2' => $from);
foreach ($fileareas as $fileareaname) {
$files = $fs->get_area_files($context->id, $component, $fileareaname, false, "itemid", true, $extrasql, $extraconditions);
$updates->{$fileareaname . 'files'} = !empty($files);
}
}
// Check completion.
$updates->completion = false;
$supportcompletion = plugin_supports('mod', $cm->modname, FEATURE_COMPLETION_HAS_RULES);
$supportcompletion = $supportcompletion or plugin_supports('mod', $cm->modname, FEATURE_COMPLETION_TRACKS_VIEWS);
if ($supportcompletion and (empty($filter) or in_array('completion', $filter))) {
$completion = new completion_info($course);
// Use wholecourse to cache all the modules the first time.
$completiondata = $completion->get_data($cm, true);
$updates->completion = !empty($completiondata->timemodified) && $completiondata->timemodified > $from;
}
// Check grades.
$updates->gradeitems = false;
$updates->outcomes = false;
$supportgrades = plugin_supports('mod', $cm->modname, FEATURE_GRADE_HAS_GRADE);
$supportgrades = $supportgrades or plugin_supports('mod', $cm->modname, FEATURE_GRADE_OUTCOMES);
if ($supportgrades and (empty($filter) or (in_array('gradeitems', $filter) or in_array('outcomes', $filter)))) {
require_once($CFG->libdir . '/gradelib.php');
$grades = grade_get_grades($course->id, 'mod', $cm->modname, $mod->id, $USER->id);
if (empty($filter) or in_array('gradeitems', $filter)) {
foreach ($grades->items as $gradeitem) {
foreach ($gradeitem->grades as $grade) {
if ($grade->datesubmitted > $from or $grade->dategraded > $from) {
$updates->gradeitems = true;
break 2;
}
}
}
}
if (empty($filter) or in_array('outcomes', $filter)) {
foreach ($grades->outcomes as $outcome) {
foreach ($outcome->grades as $grade) {
if ($grade->datesubmitted > $from or $grade->dategraded > $from) {
$updates->outcomes = true;
break 2;
}
}
}
}
}
// Check comments.
$updates->comments = false;
if (plugin_supports('mod', $cm->modname, FEATURE_COMMENT) and (empty($filter) or in_array('comments', $filter))) {
require_once($CFG->dirroot . '/comment/locallib.php');
$manager = new comment_manager();
$updates->comments = count($manager->get_component_comments_since($course, $cm, $context, $component, $from)) > 0;
}
// Check ratings.
$updates->ratings = false;
if (plugin_supports('mod', $cm->modname, FEATURE_RATE) and (empty($filter) or in_array('ratings', $filter))) {
require_once($CFG->dirroot . '/rating/lib.php');
$manager = new rating_manager();
$updates->ratings = count($manager->get_component_ratings_since($course, $cm, $context, $component, $from)) > 0;
}
return $updates;
}
......@@ -3297,4 +3297,92 @@ class core_course_courselib_testcase extends advanced_testcase {
]
];
}
public function test_course_check_module_updates_since() {
global $CFG, $DB, $USER;
require_once($CFG->dirroot . '/mod/glossary/lib.php');
require_once($CFG->dirroot . '/rating/lib.php');
require_once($CFG->dirroot . '/comment/lib.php');
$this->resetAfterTest(true);
$CFG->enablecompletion = true;
$course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
$glossary = $this->getDataGenerator()->create_module('glossary', array(
'course' => $course->id,
'completion' => COMPLETION_TRACKING_AUTOMATIC,
'completionview' => 1,
'allowcomments' => 1,
'assessed' => RATING_AGGREGATE_AVERAGE,
'scale' => 100
));
$glossarygenerator = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
$context = context_module::instance($glossary->cmid);
$modinfo = get_fast_modinfo($course);
$cm = $modinfo->get_cm($glossary->cmid);
$user = $this->getDataGenerator()->create_user();
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
$this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id);
$from = time();
$teacher = $this->getDataGenerator()->create_user();
$teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
$this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
assign_capability('mod/glossary:viewanyrating', CAP_ALLOW, $studentrole->id, $context->id, true);
// Check nothing changed right now.
$updates = course_check_module_updates_since($cm, $from);
$this->assertFalse($updates->configuration);
$this->assertFalse($updates->completion);
$this->assertFalse($updates->gradeitems);
$this->assertFalse($updates->comments);
$this->assertFalse($updates->ratings);
$this->assertFalse($updates->introfiles);
$this->assertFalse($updates->outcomes);
$this->waitForSecond();
// Do some changes.
$this->setUser($user);
$entry = $glossarygenerator->create_content($glossary);
$this->setUser($teacher);
// Name.
set_coursemodule_name($glossary->cmid, 'New name');
// Add some ratings.
$rm = new rating_manager();
$result = $rm->add_rating($cm, $context, 'mod_glossary', 'entry', $entry->id, 100, 50, $user->id, RATING_AGGREGATE_AVERAGE);
// Change grades.
$glossary->cmidnumber = $glossary->cmid;
glossary_update_grades($glossary, $user->id);
$this->setUser($user);
// Completion status.
glossary_view($glossary, $course, $cm, $context, 'letter');
// Add one comment.
$args = new stdClass;
$args->context = $context;
$args->course = $course;
$args->cm = $cm;
$args->area = 'glossary_entry';
$args->itemid = $entry->id;
$args->client_id = 1;
$args->component = 'mod_glossary';
$manager = new comment($args);
$manager->add('blah blah blah');
// Check upgrade status.
$updates = course_check_module_updates_since($cm, $from);
$this->assertTrue($updates->configuration);
$this->assertTrue($updates->completion);
$this->assertTrue($updates->gradeitems);
$this->assertTrue($updates->comments);
$this->assertTrue($updates->ratings);
$this->assertFalse($updates->introfiles);
$this->assertFalse($updates->outcomes);
}
}
......@@ -1517,3 +1517,28 @@ function mod_assign_output_fragment_gradingpanel($args) {
return $assign->view('gradingpanel', $viewargs);
}
/**
* Check if the module has any update that affects the current user since a given time.
*
* @param cm_info $cm course module data
* @param int $from the time to check updates from
* @param array $filter if we need to check only specific updates
* @return stdClass an object with the different type of areas indicating if they were updated or not
* @since Moodle 3.2
*/
function assign_check_updates_since(cm_info $cm, $from, $filter = array()) {
global $DB, $USER, $CFG;
require_once($CFG->dirroot . '/mod/assign/locallib.php');
$updates = new stdClass();
$updates = course_check_module_updates_since($cm, $from, array(ASSIGN_INTROATTACHMENT_FILEAREA), $filter);
// Check if there is a new submission by the user or new grades.
$select = 'assignment = :id AND userid = :userid AND (timecreated > :since1 OR timemodified > :since2)';
$params = array('id' => $cm->instance, 'userid' => $USER->id, 'since1' => $from, 'since2' => $from);
$updates->submissions = $DB->count_records_select('assign_submission', $select, $params) > 0;
$updates->grades = $DB->count_records_select('assign_grades', $select, $params) > 0;
return $updates;
}
......@@ -636,3 +636,32 @@ function book_view($book, $chapter, $islastchapter, $course, $cm, $context) {
}
}
}
/**
* Check if the module has any update that affects the current user since a given time.
*
* @param cm_info $cm course module data
* @param int $from the time to check updates from
* @param array $filter if we need to check only specific updates
* @return stdClass an object with the different type of areas indicating if they were updated or not
* @since Moodle 3.2
*/
function book_check_updates_since(cm_info $cm, $from, $filter = array()) {
global $DB;
$context = $cm->context;
$updates = new stdClass();
if (!has_capability('mod/book:read', $context)) {
return $updates;
}
$updates = course_check_module_updates_since($cm, $from, array('content'), $filter);
$select = 'bookid = :id AND (timecreated > :since1 OR timemodified > :since2)';
$params = array('id' => $cm->instance, 'since1' => $from, 'since2' => $from);
if (!has_capability('mod/book:viewhiddenchapters', $context)) {
$select .= ' AND hidden = 0';
}
$updates->entries = $DB->count_records_select('book_chapters', $select, $params) > 0;
return $updates;
}
......@@ -1131,3 +1131,34 @@ function choice_refresh_events($courseid = 0) {
return true;
}
/**
* Check if the module has any update that affects the current user since a given time.
*
* @param cm_info $cm course module data
* @param int $from the time to check updates from
* @param array $filter if we need to check only specific updates
* @return stdClass an object with the different type of areas indicating if they were updated or not
* @since Moodle 3.2
*/
function choice_check_updates_since(cm_info $cm, $from, $filter = array()) {
global $DB;
$updates = new stdClass();
$choice = $DB->get_record($cm->modname, array('id' => $cm->instance), '*', MUST_EXIST);
list($available, $warnings) = choice_get_availability_status($choice);
if (!$available) {
return $updates;
}
$updates = course_check_module_updates_since($cm, $from, array(), $filter);
if (!choice_can_view_results($choice)) {
return $updates;
}
// Check if there are new responses in the choice.
$select = 'choiceid = :id AND timemodified > :since';
$params = array('id' => $choice->id, 'since' => $from);
$updates->answers = $DB->count_records_select('choice_answers', $select, $params) > 0;
return $updates;
}
......@@ -746,3 +746,17 @@ function folder_print_recent_activity($course, $viewfullnames, $timestart) {
echo $list;
return true;
}
/**
* Check if the module has any update that affects the current user since a given time.
*
* @param cm_info $cm course module data
* @param int $from the time to check updates from
* @param array $filter if we need to check only specific updates
* @return stdClass an object with the different type of areas indicating if they were updated or not
* @since Moodle 3.2
*/
function folder_check_updates_since(cm_info $cm, $from, $filter = array()) {
$updates = course_check_module_updates_since($cm, $from, array('content'), $filter);
return $updates;
}
......@@ -2521,10 +2521,13 @@ function forum_count_discussions($forum, $cm, $course) {
* @param int $perpage
* @param int $groupid if groups enabled, get discussions for this group overriding the current group.
* Use FORUM_POSTS_ALL_USER_GROUPS for all the user groups
* @param string $extrasql additional SQL code for the query
* @param array $extraparams additional params for the $extrasql
* @return array
*/
function forum_get_discussions($cm, $forumsort="", $fullpost=true, $unused=-1, $limit=-1,
$userlastmodified=false, $page=-1, $perpage=0, $groupid = -1) {
$userlastmodified=false, $page=-1, $perpage=0, $groupid = -1,
$extrasql = '', $extraparams = array()) {
global $CFG, $DB, $USER;
$timelimit = '';
......@@ -2643,8 +2646,11 @@ function forum_get_discussions($cm, $forumsort="", $fullpost=true, $unused=-1, $
JOIN {user} u ON p.userid = u.id
$umtable
WHERE d.forum = ? AND p.parent = 0
$timelimit $groupselect
$timelimit $groupselect $extrasql
ORDER BY $forumsort, d.id DESC";
if (!empty($extrasql)) {
$params = array_merge($params, $extraparams);
}
return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
}
......@@ -8038,3 +8044,31 @@ function forum_discussion_is_locked($forum, $discussion) {
return false;
}
/**
* Check if the module has any update that affects the current user since a given time.
*
* @param cm_info $cm course module data
* @param stdClass $context context object
* @param array $filter if we need to check only specific updates
* @return stdClass an object with the different type of areas indicating if they were updated or not
* @since Moodle 3.2
*/
function forum_check_updates_since(cm_info $cm, $from, $filter = array()) {
$context = $cm->context;
$updates = new stdClass();
if (!has_capability('mod/forum:viewdiscussion', $context)) {
return $updates;
}
$updates = course_check_module_updates_since($cm, $from, array(), $filter);
// Check if there are new discussions in the forum.
$sql = 'AND (d.timemodified > ? OR p.created > ? OR p.modified > ?)';
$params = array($from, $from, $from);
$discussions = forum_get_discussions($cm, '', false, -1, -1, true, -1, 0, FORUM_POSTS_ALL_USER_GROUPS, $sql, $params);
$updates->discussions = count($discussions) > 0;
return $updates;
}
......@@ -4101,3 +4101,27 @@ function glossary_edit_entry($entry, $course, $cm, $glossary, $context) {
}
return $entry;
}
/**
* Check if the module has any update that affects the current user since a given time.
*
* @param cm_info $cm course module data
* @param int $from the time to check updates from
* @param array $filter if we need to check only specific updates
* @return stdClass an object with the different type of areas indicating if they were updated or not
* @since Moodle 3.2
*/
function glossary_check_updates_since(cm_info $cm, $from, $filter = array()) {
global $DB;
$updates = course_check_module_updates_since($cm, $from, array('attachment', 'entry'), $filter);
$select = 'glossaryid = :id AND (timecreated > :since1 OR timemodified > :since2)';
$params = array('id' => $cm->instance, 'since1' => $from, 'since2' => $from);
if (!has_capability('mod/glossary:approve', $cm->context)) {
$select .= ' AND approved = 1';
}
$updates->entries = $DB->count_records_select('glossary_entries', $select, $params) > 0;
return $updates;
}
......@@ -444,3 +444,17 @@ function imscp_view($imscp, $course, $cm, $context) {
$completion = new completion_info($course);
$completion->set_module_viewed($cm);
}
/**
* Check if the module has any update that affects the current user since a given time.
*
* @param cm_info $cm course module data
* @param int $from the time to check updates from
* @param array $filter if we need to check only specific updates
* @return stdClass an object with the different type of areas indicating if they were updated or not
* @since Moodle 3.2
*/
function imscp_check_updates_since(cm_info $cm, $from, $filter = array()) {
$updates = course_check_module_updates_since($cm, $from, array('content'), $filter);
return $updates;
}
......@@ -324,3 +324,17 @@ function label_generate_resized_image(stored_file $file, $maxwidth, $maxheight)
return $img;
}
}
/**
* Check if the module has any update that affects the current user since a given time.
*
* @param cm_info $cm course module data
* @param int $from the time to check updates from
* @param array $filter if we need to check only specific updates
* @return stdClass an object with the different type of areas indicating if they were updated or not
* @since Moodle 3.2
*/
function label_check_updates_since(cm_info $cm, $from, $filter = array()) {
$updates = course_check_module_updates_since($cm, $from, array(), $filter);
return $updates;
}
......@@ -560,3 +560,25 @@ function lti_view($lti, $course, $cm, $context) {
$completion = new completion_info($course);
$completion->set_module_viewed($cm);
}
/**
* Check if the module has any update that affects the current user since a given time.
*
* @param cm_info $cm course module data
* @param int $from the time to check updates from
* @param array $filter if we need to check only specific updates
* @return stdClass an object with the different type of areas indicating if they were updated or not
* @since Moodle 3.2
*/
function lti_check_updates_since(cm_info $cm, $from, $filter = array()) {
global $DB, $USER;
$updates = course_check_module_updates_since($cm, $from, array(), $filter);
// Check if there is a new submission.
$select = 'ltiid = :id AND userid = :userid AND (datesubmitted > :since1 OR dateupdated > :since2)';
$params = array('id' => $cm->instance, 'userid' => $USER->id, 'since1' => $from, 'since2' => $from);
$updates->submissions = $DB->count_records_select('lti_submission', $select, $params) > 0;
return $updates;
}
......@@ -508,3 +508,17 @@ function page_view($page, $course, $cm, $context) {
$completion = new completion_info($course);
$completion->set_module_viewed($cm);
}
/**
* Check if the module has any update that affects the current user since a given time.
*
* @param cm_info $cm course module data
* @param int $from the time to check updates from
* @param array $filter if we need to check only specific updates
* @return stdClass an object with the different type of areas indicating if they were updated or not
* @since Moodle 3.2
*/
function page_check_updates_since(cm_info $cm, $from, $filter = array()) {
$updates = course_check_module_updates_since($cm, $from, array('content'), $filter);
return $updates;
}
......@@ -1908,3 +1908,42 @@ function quiz_get_completion_state($course, $cm, $userid, $type) {
}
return false;
}
/**
* Check if the module has any update that affects the current user since a given time.
*
* @param cm_info $cm course module data
* @param int $from the time to check updates from
* @param array $filter if we need to check only specific updates
* @return stdClass an object with the different type of areas indicating if they were updated or not
* @since Moodle 3.2
*/
function quiz_check_updates_since(cm_info $cm, $from, $filter = array()) {
global $DB, $USER, $CFG;
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
$updates = course_check_module_updates_since($cm, $from, array(), $filter);
// Check if questions were updated.
$quizobj = quiz::create($cm->instance, $USER->id);
$quizobj->preload_questions();
$quizobj->load_questions();
$questionids = array_keys($quizobj->get_questions());
if (!empty($questionids)) {
list($questionsql, $params) = $DB->get_in_or_equal($questionids, SQL_PARAMS_NAMED);
$select = 'id ' . $questionsql . ' AND (timemodified > :time1 OR timecreated > :time2)';
$params['time1'] = $from;
$params['time2'] = $from;
$updates->questions = $DB->count_records_select('question', $select, $params) > 0;
} else {
$updates->questions = false;
}
// Check for new attempts or grades.
$select = 'quiz = ? AND userid = ? AND timemodified > ?';
$params = array($cm->instance, $USER->id, $from);
$updates->attempts = $DB->count_records_select('quiz_attempts', $select, $params) > 0;
$updates->grades = $DB->count_records_select('quiz_grades', $select, $params) > 0;
return $updates;
}
......@@ -519,3 +519,17 @@ function resource_view($resource, $course, $cm, $context) {
$completion = new completion_info($course);
$completion->set_module_viewed($cm);
}
/**
* Check if the module has any update that affects the current user since a given time.