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

This commit is contained in:
David Monllao 2016-10-25 17:33:08 +08:00
commit 26aa08b2f8
28 changed files with 1044 additions and 8 deletions

View File

@ -217,4 +217,50 @@ class comment_manager {
}
return true;
}
/**
* Get comments created since a given time.
*
* @param stdClass $course course object
* @param stdClass $context context object
* @param string $component component name
* @param int $since the time to check
* @param stdClass $cm course module object
* @return array list of comments db records since the given timelimit
* @since Moodle 3.2
*/
public function get_component_comments_since($course, $context, $component, $since, $cm = null) {
global $DB;
$commentssince = array();
$where = 'contextid = ? AND component = ? AND timecreated > ?';
$comments = $DB->get_records_select('comments', $where, array($context->id, $component, $since));
// Check item by item if we have permissions.
$managersviewstatus = array();
foreach ($comments as $comment) {
// Check if the manager for the item is cached.
if (!isset($managersviewstatus[$comment->commentarea]) or
!isset($managersviewstatus[$comment->commentarea][$comment->itemid])) {
$args = new stdClass;
$args->area = $comment->commentarea;
$args->itemid = $comment->itemid;
$args->context = $context;
$args->course = $course;
$args->client_id = 0;
$args->component = $component;
if (!empty($cm)) {
$args->cm = $cm;
}
$manager = new comment($args);
$managersviewstatus[$comment->commentarea][$comment->itemid] = $manager->can_view();
}
if ($managersviewstatus[$comment->commentarea][$comment->itemid]) {
$commentssince[$comment->id] = $comment;
}
}
return $commentssince;
}
}

View File

@ -3021,4 +3021,132 @@ class core_course_external extends external_api {
)
);
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 3.2
*/
public static function check_updates_parameters() {
return new external_function_parameters(
array(
'courseid' => new external_value(PARAM_INT, 'Course id to check'),
'tocheck' => new external_multiple_structure(
new external_single_structure(
array(
'contextlevel' => new external_value(PARAM_ALPHA, 'The context level for the file location.
Only module supported right now.'),
'id' => new external_value(PARAM_INT, 'Context instance id'),
'since' => new external_value(PARAM_INT, 'Check updates since this time stamp'),
)
),
'Instances to check'
),
'filter' => new external_multiple_structure(
new external_value(PARAM_ALPHANUM, 'Area name: configuration, fileareas, completion, ratings, comments,
gradeitems, outcomes'),
'Check only for updates in these areas', VALUE_DEFAULT, array()
)
)
);
}
/**
* Check if there is updates affecting the user for the given course and contexts.
* Right now only modules are supported.
* This WS calls mod_check_updates_since for each module to check if there is any update the user should we aware of.
*
* @param int $courseid the list of modules to check
* @param array $tocheck the list of modules to check
* @param array $filter check only for updates in these areas
* @return array list of updates and warnings
* @throws moodle_exception
* @since Moodle 3.2
*/
public static function check_updates($courseid, $tocheck, $filter = array()) {
global $CFG, $DB;
$params = self::validate_parameters(
self::check_updates_parameters(),
array(
'courseid' => $courseid,
'tocheck' => $tocheck,
'filter' => $filter,
)
);
$course = get_course($params['courseid']);
$context = context_course::instance($course->id);
self::validate_context($context);
list($instances, $warnings) = course_check_updates($course, $params['tocheck'], $filter);
$instancesformatted = array();
foreach ($instances as $instance) {
$updates = array();
foreach ($instance['updates'] as $name => $data) {
if (empty($data->updated)) {
continue;
}
$updatedata = array(
'name' => $name,
);
if (!empty($data->timeupdated)) {
$updatedata['timeupdated'] = $data->timeupdated;
}
if (!empty($data->itemids)) {
$updatedata['itemids'] = $data->itemids;
}
$updates[] = $updatedata;
}
if (!empty($updates)) {
$instancesformatted[] = array(
'contextlevel' => $instance['contextlevel'],
'id' => $instance['id'],
'updates' => $updates
);
}
}
return array(
'instances' => $instancesformatted,
'warnings' => $warnings
);
}
/**
* Returns description of method result value
*
* @return external_description
* @since Moodle 3.2
*/
public static function check_updates_returns() {
return new external_single_structure(
array(
'instances' => new external_multiple_structure(
new external_single_structure(
array(
'contextlevel' => new external_value(PARAM_ALPHA, 'The context level'),
'id' => new external_value(PARAM_INT, 'Instance id'),
'updates' => new external_multiple_structure(
new external_single_structure(
array(
'name' => new external_value(PARAM_ALPHANUMEXT, 'Name of the area updated.'),
'timeupdated' => new external_value(PARAM_INT, 'Last time was updated', VALUE_OPTIONAL),
'itemids' => new external_multiple_structure(
new external_value(PARAM_INT, 'Instance id'),
'The ids of the items updated',
VALUE_OPTIONAL
)
)
)
)
)
)
),
'warnings' => new external_warnings()
)
);
}
}

View File

@ -3606,3 +3606,192 @@ 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.
try {
$cm = $modinfo->get_cm($instance['id']);
} catch (Exception $e) {
$warnings[] = array(
'item' => 'module',
'itemid' => $instance['id'],
'warningcode' => 'cmidnotincourse',
'message' => 'This module id does not belong to this course.'
);
continue;
}
if (!$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 = (object) array('updated' => false);
if ($updates->configuration->updated = $mod->timemodified > $from) {
$updates->configuration->timeupdated = $mod->timemodified;
}
}
// 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();
$files = $fs->get_area_files($context->id, $component, $fileareas, false, "filearea, timemodified DESC", true, $from);
foreach ($fileareas as $filearea) {
$updates->{$filearea . 'files'} = (object) array('updated' => false);
}
foreach ($files as $file) {
$updates->{$file->get_filearea() . 'files'}->updated = true;
$updates->{$file->get_filearea() . 'files'}->itemids[] = $file->get_id();
}
}
// Check completion.
$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))) {
$updates->completion = (object) array('updated' => false);
$completion = new completion_info($course);
// Use wholecourse to cache all the modules the first time.
$completiondata = $completion->get_data($cm, true);
if ($updates->completion->updated = !empty($completiondata->timemodified) && $completiondata->timemodified > $from) {
$updates->completion->timemodified = $completiondata->timemodified;
}
}
// Check grades.
$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)) {
$updates->gradeitems = (object) array('updated' => false);
foreach ($grades->items as $gradeitem) {
foreach ($gradeitem->grades as $grade) {
if ($grade->datesubmitted > $from or $grade->dategraded > $from) {
$updates->gradeitems->updated = true;
$updates->gradeitems->itemids[] = $gradeitem->id;
}
}
}
}
if (empty($filter) or in_array('outcomes', $filter)) {
$updates->outcomes = (object) array('updated' => false);
foreach ($grades->outcomes as $outcome) {
foreach ($outcome->grades as $grade) {
if ($grade->datesubmitted > $from or $grade->dategraded > $from) {
$updates->outcomes->updated = true;
$updates->outcomes->itemids[] = $outcome->id;
}
}
}
}
}
// Check comments.
if (plugin_supports('mod', $cm->modname, FEATURE_COMMENT) and (empty($filter) or in_array('comments', $filter))) {
$updates->comments = (object) array('updated' => false);
require_once($CFG->dirroot . '/comment/locallib.php');
$manager = new comment_manager();
$comments = $manager->get_component_comments_since($course, $context, $component, $from, $cm);
if (!empty($comments)) {
$updates->comments->updated = true;
$updates->comments->itemids = array_keys($comments);
}
}
// Check ratings.
if (plugin_supports('mod', $cm->modname, FEATURE_RATE) and (empty($filter) or in_array('ratings', $filter))) {
$updates->ratings = (object) array('updated' => false);
require_once($CFG->dirroot . '/rating/lib.php');
$manager = new rating_manager();
$ratings = $manager->get_component_ratings_since($context, $component, $from);
if (!empty($ratings)) {
$updates->ratings->updated = true;
$updates->ratings->itemids = array_keys($ratings);
}
}
return $updates;
}

View File

@ -3291,4 +3291,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->updated);
$this->assertFalse($updates->completion->updated);
$this->assertFalse($updates->gradeitems->updated);
$this->assertFalse($updates->comments->updated);
$this->assertFalse($updates->ratings->updated);
$this->assertFalse($updates->introfiles->updated);
$this->assertFalse($updates->outcomes->updated);
$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->updated);
$this->assertTrue($updates->completion->updated);
$this->assertTrue($updates->gradeitems->updated);
$this->assertTrue($updates->comments->updated);
$this->assertTrue($updates->ratings->updated);
$this->assertFalse($updates->introfiles->updated);
$this->assertFalse($updates->outcomes->updated);
}
}

View File

@ -2096,4 +2096,82 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
$result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
$this->assertCount(0, $result['courses']);
}
public function test_check_updates() {
global $DB;
$this->resetAfterTest(true);
$this->setAdminUser();
// Create different types of activities.
$course = self::getDataGenerator()->create_course();
$tocreate = array('assign', 'book', 'choice', 'folder', 'forum', 'glossary', 'imscp', 'label', 'lti', 'page', 'quiz',
'resource', 'scorm', 'survey', 'url', 'wiki');
$modules = array();
foreach ($tocreate as $modname) {
$modules[$modname]['instance'] = $this->getDataGenerator()->create_module($modname, array('course' => $course->id));
$modules[$modname]['cm'] = get_coursemodule_from_id(false, $modules[$modname]['instance']->cmid);
$modules[$modname]['context'] = context_module::instance($modules[$modname]['instance']->cmid);
}
$student = self::getDataGenerator()->create_user();
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
self::getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id);
$this->setUser($student);
$since = time();
$this->waitForSecond();
$params = array();
foreach ($modules as $modname => $data) {
$params[$data['cm']->id] = array(
'contextlevel' => 'module',
'id' => $data['cm']->id,
'since' => $since
);
}
// Check there is nothing updated because modules are fresh new.
$result = core_course_external::check_updates($course->id, $params);
$result = external_api::clean_returnvalue(core_course_external::check_updates_returns(), $result);
$this->assertCount(0, $result['instances']);
$this->assertCount(0, $result['warnings']);
// Update a module after a second.
$this->waitForSecond();
set_coursemodule_name($modules['forum']['cm']->id, 'New forum name');
$found = false;
$result = core_course_external::check_updates($course->id, $params);
$result = external_api::clean_returnvalue(core_course_external::check_updates_returns(), $result);
$this->assertCount(1, $result['instances']);
$this->assertCount(0, $result['warnings']);
foreach ($result['instances'] as $module) {
foreach ($module['updates'] as $update) {
if ($module['id'] == $modules['forum']['cm']->id and $update['name'] == 'configuration') {
$found = true;
}
}
}
$this->assertTrue($found);
// Do not retrieve the configuration field.
$filter = array('files');
$found = false;
$result = core_course_external::check_updates($course->id, $params, $filter);
$result = external_api::clean_returnvalue(core_course_external::check_updates_returns(), $result);
$this->assertCount(0, $result['instances']);
$this->assertCount(0, $result['warnings']);
$this->assertFalse($found);
// Add invalid cmid.
$params[] = array(
'contextlevel' => 'module',
'id' => -2,
'since' => $since
);
$result = core_course_external::check_updates($course->id, $params);
$result = external_api::clean_returnvalue(core_course_external::check_updates_returns(), $result);
$this->assertCount(1, $result['warnings']);
$this->assertEquals(-2, $result['warnings'][0]['itemid']);
}
}

View File

@ -335,6 +335,15 @@ $functions = array(
'type' => 'read',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
'core_course_check_updates' => array(
'classname' => 'core_course_external',
'methodname' => 'check_updates',
'classpath' => 'course/externallib.php',
'description' => 'Check if there is updates affecting the user for the given course and contexts.',
'type' => 'read',
'ajax' => true,
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
'core_enrol_get_course_enrolment_methods' => array(
'classname' => 'core_enrol_external',
'methodname' => 'get_course_enrolment_methods',

View File

@ -794,30 +794,44 @@ class file_storage {
*
* @param int $contextid context ID
* @param string $component component
* @param string $filearea file area
* @param mixed $filearea file area/s, you cannot specify multiple fileareas as well as an itemid
* @param int $itemid item ID or all files if not specified
* @param string $sort A fragment of SQL to use for sorting
* @param bool $includedirs whether or not include directories
* @param int $updatedsince return files updated since this time
* @return stored_file[] array of stored_files indexed by pathanmehash
*/
public function get_area_files($contextid, $component, $filearea, $itemid = false, $sort = "itemid, filepath, filename", $includedirs = true) {
public function get_area_files($contextid, $component, $filearea, $itemid = false, $sort = "itemid, filepath, filename",
$includedirs = true, $updatedsince = 0) {
global $DB;
$conditions = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea);
if ($itemid !== false) {
list($areasql, $conditions) = $DB->get_in_or_equal($filearea, SQL_PARAMS_NAMED);
$conditions['contextid'] = $contextid;
$conditions['component'] = $component;
if ($itemid !== false && is_array($filearea)) {
throw new coding_exception('You cannot specify multiple fileareas as well as an itemid.');
} else if ($itemid !== false) {
$itemidsql = ' AND f.itemid = :itemid ';
$conditions['itemid'] = $itemid;
} else {
$itemidsql = '';
}
$updatedsincesql = '';
if (!empty($updatedsince)) {
$conditions['time'] = $updatedsince;
$updatedsincesql = 'AND f.timemodified > :time';
}
$sql = "SELECT ".self::instance_sql_fields('f', 'r')."
FROM {files} f
LEFT JOIN {files_reference} r
ON f.referencefileid = r.id
WHERE f.contextid = :contextid
AND f.component = :component
AND f.filearea = :filearea
AND f.filearea $areasql
$updatedsincesql
$itemidsql";
if (!empty($sort)) {
$sql .= " ORDER BY {$sort}";

View File

@ -583,6 +583,8 @@ function grade_get_grades($courseid, $itemtype, $itemmodule, $iteminstance, $use
$grade->feedback = $grade_grades[$userid]->feedback;
$grade->feedbackformat = $grade_grades[$userid]->feedbackformat;
$grade->usermodified = $grade_grades[$userid]->usermodified;
$grade->datesubmitted = $grade_grades[$userid]->get_datesubmitted();
$grade->dategraded = $grade_grades[$userid]->get_dategraded();
// create text representation of grade
if (in_array($grade_item->id, $needsupdate)) {

View File

@ -223,6 +223,8 @@ function assign_supports($feature) {
return true;
case FEATURE_PLAGIARISM:
return true;
case FEATURE_COMMENT:
return true;
default:
return null;
@ -1515,3 +1517,39 @@ 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 = (object) array('updated' => false);
$submissions = $DB->get_records_select('assign_submission', $select, $params, '', 'id');
if (!empty($submissions)) {
$updates->submissions->updated = true;
$updates->submissions->itemids = array_keys($submissions);
}
$updates->grades = (object) array('updated' => false);
$grades = $DB->get_records_select('assign_grades', $select, $params, '', 'id');
if (!empty($grades)) {
$updates->grades->updated = true;
$updates->grades->itemids = array_keys($grades);
}
return $updates;
}

View File

@ -636,3 +636,37 @@ 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 = (object) array('updated' => false);
$entries = $DB->get_records_select('book_chapters', $select, $params, '', 'id');
if (!empty($entries)) {
$updates->entries->updated = true;
$updates->entries->itemids = array_keys($entries);
}
return $updates;
}

View File

@ -1131,3 +1131,39 @@ 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.
$updates->answers = (object) array('updated' => false);
$select = 'choiceid = :id AND timemodified > :since';
$params = array('id' => $choice->id, 'since' => $from);
$answers = $DB->get_records_select('choice_answers', $select, $params, '', 'id');
if (!empty($answers)) {
$updates->answers->updated = true;
$updates->answers->itemids = array_keys($answers);
}
return $updates;
}

View File

@ -2861,6 +2861,7 @@ function data_supports($feature) {
case FEATURE_RATE: return true;
case FEATURE_BACKUP_MOODLE2: return true;
case FEATURE_SHOW_DESCRIPTION: return true;
case FEATURE_COMMENT: return true;
default: return null;
}

View File

@ -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;
}

View File

@ -2521,10 +2521,12 @@ 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 int $updatedsince retrieve only discussions updated since the given time
* @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,
$updatedsince = 0) {
global $CFG, $DB, $USER;
$timelimit = '';
@ -2635,6 +2637,12 @@ function forum_get_discussions($cm, $forumsort="", $fullpost=true, $unused=-1, $
$umtable = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
}
$updatedsincesql = '';
if (!empty($updatedsince)) {
$updatedsincesql = 'AND d.timemodified > ?';
$params[] = $updatedsince;
}
$allnames = get_all_user_name_fields(true, 'u');
$sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend, d.pinned, $allnames,
u.email, u.picture, u.imagealt $umfields
@ -2643,8 +2651,9 @@ 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 $updatedsincesql
ORDER BY $forumsort, d.id DESC";
return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
}
@ -8038,3 +8047,33 @@ 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 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 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.
$updates->discussions = (object) array('updated' => false);
$discussions = forum_get_discussions($cm, '', false, -1, -1, true, -1, 0, FORUM_POSTS_ALL_USER_GROUPS, $from);
if (!empty($discussions)) {
$updates->discussions->updated = true;
$updates->discussions->itemids = array_keys($discussions);
}
return $updates;
}

View File

@ -3018,6 +3018,7 @@ function glossary_supports($feature) {
case FEATURE_RATE: return true;
case FEATURE_BACKUP_MOODLE2: return true;
case FEATURE_SHOW_DESCRIPTION: return true;
case FEATURE_COMMENT: return true;
default: return null;
}
@ -4100,3 +4101,33 @@ 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);
$updates->entries = (object) array('updated' => false);
$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';
}
$entries = $DB->get_records_select('glossary_entries', $select, $params, '', 'id');
if (!empty($entries)) {
$updates->entries->updated = true;
$updates->entries->itemids = array_keys($entries);
}
return $updates;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -560,3 +560,30 @@ 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.
$updates->submissions = (object) array('updated' => false);
$select = 'ltiid = :id AND userid = :userid AND (datesubmitted > :since1 OR dateupdated > :since2)';
$params = array('id' => $cm->instance, 'userid' => $USER->id, 'since1' => $from, 'since2' => $from);
$submissions = $DB->get_records_select('lti_submission', $select, $params, '', 'id');
if (!empty($submissions)) {
$updates->submissions->updated = true;
$updates->submissions->itemids = array_keys($submissions);
}
return $updates;
}

View File

@ -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;
}

View File

@ -1908,3 +1908,56 @@ 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.
$updates->questions = (object) array('updated' => false);
$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;
$questions = $DB->count_records_select('question', $select, $params) > 0;
if (!empty($questions)) {
$updates->questions->updated = true;
$updates->questions->itemids = array_keys($questions);
}
}
// Check for new attempts or grades.
$updates->attempts = (object) array('updated' => false);
$updates->grades = (object) array('updated' => false);
$select = 'quiz = ? AND userid = ? AND timemodified > ?';
$params = array($cm->instance, $USER->id, $from);
$attempts = $DB->get_records_select('quiz_attempts', $select, $params, '', 'id');
if (!empty($attempts)) {
$updates->attempts->updated = true;
$updates->attempts->itemids = array_keys($attempts);
}
$grades = $DB->get_records_select('quiz_grades', $select, $params, '', 'id');
if (!empty($grades)) {
$updates->grades->updated = true;
$updates->grades->itemids = array_keys($grades);
}
return $updates;
}

View File

@ -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.
*
* @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 resource_check_updates_since(cm_info $cm, $from, $filter = array()) {
$updates = course_check_module_updates_since($cm, $from, array('content'), $filter);
return $updates;
}

View File

@ -1494,3 +1494,35 @@ function scorm_view($scorm, $course, $cm, $context) {
$event->add_record_snapshot('scorm', $scorm);
$event->trigger();
}
/**
* 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 scorm_check_updates_since(cm_info $cm, $from, $filter = array()) {
global $DB, $USER, $CFG;
require_once($CFG->dirroot . '/mod/scorm/locallib.php');
$scorm = $DB->get_record($cm->modname, array('id' => $cm->instance), '*', MUST_EXIST);
$updates = new stdClass();
list($available, $warnings) = scorm_get_availability_status($scorm, true, $cm->context);
if (!$available) {
return $updates;
}
$updates = course_check_module_updates_since($cm, $from, array('package'), $filter);
$updates->tracks = (object) array('updated' => false);
$select = 'scormid = ? AND userid = ? AND timemodified > ?';
$params = array($scorm->id, $USER->id, $from);
$tracks = $DB->get_records_select('scorm_scoes_track', $select, $params, '', 'id');
if (!empty($tracks)) {
$updates->tracks->updated = true;
$updates->tracks->itemids = array_keys($tracks);
}
return $updates;
}

View File

@ -1050,3 +1050,32 @@ function survey_get_completion_state($course, $cm, $userid, $type) {
return $type;
}
}
/**
* 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 survey_check_updates_since(cm_info $cm, $from, $filter = array()) {
global $DB, $USER;
$updates = new stdClass();
if (!has_capability('mod/survey:participate', $cm->context)) {
return $updates;
}
$updates = course_check_module_updates_since($cm, $from, array(), $filter);
$updates->answers = (object) array('updated' => false);
$select = 'survey = ? AND userid = ? AND time > ?';
$params = array($cm->instance, $USER->id, $from);
$answers = $DB->get_records_select('survey_answers', $select, $params, '', 'id');
if (!empty($answers)) {
$updates->answers->updated = true;
$updates->answers->itemids = array_keys($answers);
}
return $updates;
}

View File

@ -7,6 +7,8 @@ information provided here is intended especially for developers.
* update_module_button() and core_renderer::update_module_button() have been deprecated and should not be used anymore.
Activity modules should not add the edit module button, the link is already available in the Administration block.
Themes can choose to display the link in the buttons row consistently for all module types.
* New callback check_updates_since available. Check if the module has any update that affects the current user since the given time.
Please refer to mod/assign/lib.php, mod/forum/lib.php or mod/quiz/lib.php for sample code.
=== 3.1 ===

View File

@ -351,3 +351,17 @@ function url_view($url, $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 url_check_updates_since(cm_info $cm, $from, $filter = array()) {
$updates = course_check_module_updates_since($cm, $from, array('content'), $filter);
return $updates;
}

View File

@ -262,6 +262,8 @@ function wiki_supports($feature) {
return true;
case FEATURE_SHOW_DESCRIPTION:
return true;
case FEATURE_COMMENT:
return true;
default:
return null;
@ -737,3 +739,43 @@ function wiki_page_view($wiki, $page, $course, $cm, $context, $uid = null, $othe
$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 wiki_check_updates_since(cm_info $cm, $from, $filter = array()) {
global $DB, $CFG;
require_once($CFG->dirroot . '/mod/wiki/locallib.php');
$updates = new stdClass();
if (!has_capability('mod/wiki:viewpage', $cm->context)) {
return $updates;
}
$updates = course_check_module_updates_since($cm, $from, array('attachments'), $filter);
// Check only pages updated in subwikis the user can access.
$updates->pages = (object) array('updated' => false);
$wiki = $DB->get_record($cm->modname, array('id' => $cm->instance), '*', MUST_EXIST);
if ($subwikis = wiki_get_visible_subwikis($wiki, $cm, $cm->context)) {
$subwikisids = array();
foreach ($subwikis as $subwiki) {
$subwikisids[] = $subwiki->id;
}
list($subwikissql, $params) = $DB->get_in_or_equal($subwikisids, SQL_PARAMS_NAMED);
$select = 'subwikiid ' . $subwikissql . ' AND (timemodified > :since1 OR timecreated > :since2)';
$params['since1'] = $from;
$params['since2'] = $from;
$pages = $DB->get_records_select('wiki_pages', $select, $params, '', 'id');
if (!empty($pages)) {
$updates->pages->updated = true;
$updates->pages->itemids = array_keys($pages);
}
}
return $updates;
}

View File

@ -1174,6 +1174,40 @@ class rating_manager {
}
return $result;
}
/**
* Get ratings created since a given time.
*
* @param stdClass $context context object
* @param string $component component name
* @param int $since the time to check
* @return array list of ratings db records since the given timelimit
* @since Moodle 3.2
*/
public function get_component_ratings_since($context, $component, $since) {
global $DB, $USER;
$ratingssince = array();
$where = 'contextid = ? AND component = ? AND (timecreated > ? OR timemodified > ?)';
$ratings = $DB->get_records_select('rating', $where, array($context->id, $component, $since, $since));
// Check area by area if we have permissions.
$permissions = array();
$rm = new rating_manager();
foreach ($ratings as $rating) {
// Check if the permission array for the area is cached.
if (!isset($permissions[$rating->ratingarea])) {
$permissions[$rating->ratingarea] = $rm->get_plugin_permissions_array($context->id, $component,
$rating->ratingarea);
}
if (($permissions[$rating->ratingarea]['view'] and $rating->userid == $USER->id) or
($permissions[$rating->ratingarea]['viewany'] or $permissions[$rating->ratingarea]['viewall'])) {
$ratingssince[$rating->id] = $rating;
}
}
return $ratingssince;
}
} // End rating_manager class definition.
/**

View File

@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
$version = 2016102100.00; // YYYYMMDD = weekly release date of this DEV branch.
$version = 2016102100.01; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.