mirror of
https://github.com/moodle/moodle.git
synced 2025-04-13 20:42:22 +02:00
MDL-58266 core_completion: Add new view table.
This commit is contained in:
parent
e4c5a12a1c
commit
c6e018e04e
@ -1284,7 +1284,6 @@ class backup_userscompletion_structure_step extends backup_structure_step {
|
||||
protected function define_structure() {
|
||||
|
||||
// Define each element separated
|
||||
|
||||
$completions = new backup_nested_element('completions');
|
||||
|
||||
$completion = new backup_nested_element('completion', array('id'), array(
|
||||
@ -1302,8 +1301,22 @@ class backup_userscompletion_structure_step extends backup_structure_step {
|
||||
|
||||
$completion->annotate_ids('user', 'userid');
|
||||
|
||||
// Return the root element (completions)
|
||||
$completionviews = new backup_nested_element('completionviews');
|
||||
$completionview = new backup_nested_element('completionview', ['id'], ['userid', 'timecreated']);
|
||||
|
||||
// Build the tree.
|
||||
$completionviews->add_child($completionview);
|
||||
|
||||
// Define sources.
|
||||
$completionview->set_source_table('course_modules_viewed', ['coursemoduleid' => backup::VAR_MODID]);
|
||||
|
||||
// Define id annotations.
|
||||
$completionview->annotate_ids('user', 'userid');
|
||||
|
||||
$completions->add_child($completionviews);
|
||||
// Return the root element (completions).
|
||||
return $completions;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4680,8 +4680,12 @@ class restore_userscompletion_structure_step extends restore_structure_step {
|
||||
|
||||
$paths = array();
|
||||
|
||||
// Restore completion.
|
||||
$paths[] = new restore_path_element('completion', '/completions/completion');
|
||||
|
||||
// Restore completion view.
|
||||
$paths[] = new restore_path_element('completionview', '/completions/completionviews/completionview');
|
||||
|
||||
return $paths;
|
||||
}
|
||||
|
||||
@ -4710,6 +4714,29 @@ class restore_userscompletion_structure_step extends restore_structure_step {
|
||||
// Normal entry where it doesn't exist already
|
||||
$DB->insert_record('course_modules_completion', $data);
|
||||
}
|
||||
|
||||
// Add viewed to course_modules_viewed.
|
||||
if (isset($data->viewed) && $data->viewed) {
|
||||
$dataview = clone($data);
|
||||
unset($dataview->id);
|
||||
unset($dataview->viewed);
|
||||
$dataview->timecreated = $data->timemodified;
|
||||
$DB->insert_record('course_modules_viewed', $dataview);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the completioinview data.
|
||||
* @param array $data The data from the XML file.
|
||||
*/
|
||||
protected function process_completionview(array $data) {
|
||||
global $DB;
|
||||
|
||||
$data = (object)$data;
|
||||
$data->coursemoduleid = $this->task->get_moduleid();
|
||||
$data->userid = $this->get_mappingid('user', $data->userid);
|
||||
|
||||
$DB->insert_record('course_modules_viewed', $data);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -314,6 +314,41 @@ class restore_stepslib_date_test extends \restore_date_testcase {
|
||||
$this->assertEquals($coursemodulecompletion->timemodified, $newcoursemodulecompletion->timemodified);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checking that the user completion of an activity relating to the view field does not change
|
||||
* when doing a course restore.
|
||||
* @covers ::backup_and_restore
|
||||
*/
|
||||
public function test_usercompletion_view_restore() {
|
||||
global $DB;
|
||||
// More completion...
|
||||
$course = $this->getDataGenerator()->create_course(['startdate' => time(), 'enablecompletion' => 1]);
|
||||
$student = $this->getDataGenerator()->create_user();
|
||||
$this->getDataGenerator()->enrol_user($student->id, $course->id, 'student');
|
||||
$assign = $this->getDataGenerator()->create_module('assign', [
|
||||
'course' => $course->id,
|
||||
'completion' => COMPLETION_TRACKING_AUTOMATIC, // Show activity as complete when conditions are met.
|
||||
'completionview' => 1
|
||||
]);
|
||||
$cm = $DB->get_record('course_modules', ['course' => $course->id, 'instance' => $assign->id]);
|
||||
|
||||
// Mark the activity as completed.
|
||||
$completion = new \completion_info($course);
|
||||
$completion->set_module_viewed($cm, $student->id);
|
||||
|
||||
$coursemodulecompletion = $DB->get_record('course_modules_viewed', ['coursemoduleid' => $cm->id]);
|
||||
|
||||
// Back up and restore.
|
||||
$newcourseid = $this->backup_and_restore($course);
|
||||
$newcourse = get_course($newcourseid);
|
||||
|
||||
$assignid = $DB->get_field('assign', 'id', ['course' => $newcourseid]);
|
||||
$cm = $DB->get_record('course_modules', ['course' => $newcourse->id, 'instance' => $assignid]);
|
||||
$newcoursemodulecompletion = $DB->get_record('course_modules_viewed', ['coursemoduleid' => $cm->id]);
|
||||
|
||||
$this->assertEquals($coursemodulecompletion->timecreated, $newcoursemodulecompletion->timecreated);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensuring that the timemodified field of the question attempt steps table does not change when
|
||||
* a course restore is done.
|
||||
|
@ -66,10 +66,14 @@ class provider implements
|
||||
'userid' => 'privacy:metadata:userid',
|
||||
'coursemoduleid' => 'privacy:metadata:coursemoduleid',
|
||||
'completionstate' => 'privacy:metadata:completionstate',
|
||||
'viewed' => 'privacy:metadata:viewed',
|
||||
'overrideby' => 'privacy:metadata:overrideby',
|
||||
'timemodified' => 'privacy:metadata:timemodified'
|
||||
], 'privacy:metadata:coursemodulesummary');
|
||||
$collection->add_database_table('course_modules_viewed', [
|
||||
'userid' => 'privacy:metadata:userid',
|
||||
'coursemoduleid' => 'privacy:metadata:coursemoduleid',
|
||||
'timecreated' => 'privacy:metadata:timecreated',
|
||||
], 'privacy:metadata:coursemodulesummary');
|
||||
$collection->add_database_table('course_completion_crit_compl', [
|
||||
'userid' => 'privacy:metadata:userid',
|
||||
'course' => 'privacy:metadata:course',
|
||||
@ -91,16 +95,18 @@ class provider implements
|
||||
public static function get_course_completion_join_sql(int $userid, string $prefix, string $joinfield) : array {
|
||||
$cccalias = "{$prefix}_ccc"; // Course completion criteria.
|
||||
$cmcalias = "{$prefix}_cmc"; // Course modules completion.
|
||||
$cmvalias = "{$prefix}_cmv"; // Course modules viewed.
|
||||
$ccccalias = "{$prefix}_cccc"; // Course completion criteria completion.
|
||||
|
||||
$join = "JOIN {course_completion_criteria} {$cccalias} ON {$joinfield} = {$cccalias}.course
|
||||
LEFT JOIN {course_modules_completion} {$cmcalias} ON {$cccalias}.moduleinstance = {$cmcalias}.coursemoduleid
|
||||
AND {$cmcalias}.userid = :{$prefix}_moduleuserid
|
||||
LEFT JOIN {course_modules_viewed} {$cmvalias} ON {$cccalias}.moduleinstance = {$cmvalias}.coursemoduleid
|
||||
AND {$cmvalias}.userid = :{$prefix}_moduleuserid2
|
||||
LEFT JOIN {course_completion_crit_compl} {$ccccalias} ON {$ccccalias}.criteriaid = {$cccalias}.id
|
||||
AND {$ccccalias}.userid = :{$prefix}_courseuserid";
|
||||
$where = "{$cmcalias}.id IS NOT NULL OR {$ccccalias}.id IS NOT NULL";
|
||||
$params = ["{$prefix}_moduleuserid" => $userid, "{$prefix}_courseuserid" => $userid];
|
||||
|
||||
$where = "{$cmcalias}.id IS NOT NULL OR {$ccccalias}.id IS NOT NULL OR {$cmvalias}.id IS NOT NULL";
|
||||
$params = ["{$prefix}_moduleuserid" => $userid, "{$prefix}_moduleuserid2" => $userid, "{$prefix}_courseuserid" => $userid];
|
||||
return [$join, $where, $params];
|
||||
}
|
||||
|
||||
@ -126,6 +132,14 @@ class provider implements
|
||||
|
||||
$userlist->add_from_sql('userid', $sql, $params);
|
||||
|
||||
$sql = "SELECT cmv.userid
|
||||
FROM {course} c
|
||||
JOIN {course_completion_criteria} ccc ON ccc.course = c.id
|
||||
JOIN {course_modules_viewed} cmv ON cmv.coursemoduleid = ccc.moduleinstance
|
||||
WHERE c.id = :courseid";
|
||||
|
||||
$userlist->add_from_sql('userid', $sql, $params);
|
||||
|
||||
$sql = "SELECT ccc_compl.userid
|
||||
FROM {course} c
|
||||
JOIN {course_completion_criteria} ccc ON ccc.course = c.id
|
||||
@ -231,6 +245,7 @@ class provider implements
|
||||
if (isset($courseid)) {
|
||||
|
||||
$usersql = isset($user) ? 'AND cmc.userid = :userid' : '';
|
||||
$usercmvsql = isset($user) ? 'AND cmv.userid = :userid' : '';
|
||||
$params = isset($user) ? ['course' => $courseid, 'userid' => $user->id] : ['course' => $courseid];
|
||||
|
||||
// Find records relating to course modules.
|
||||
@ -245,6 +260,19 @@ class provider implements
|
||||
$deletesql = 'id ' . $deletesql;
|
||||
$DB->delete_records_select('course_modules_completion', $deletesql, $deleteparams);
|
||||
}
|
||||
// Find records relating to course modules completion viewed.
|
||||
$sql = "SELECT cmv.id
|
||||
FROM {course_completion_criteria} ccc
|
||||
JOIN {course_modules_viewed} cmv ON ccc.moduleinstance = cmv.coursemoduleid
|
||||
WHERE ccc.course = :course $usercmvsql";
|
||||
$recordids = $DB->get_records_sql($sql, $params);
|
||||
$ids = array_keys($recordids);
|
||||
if (!empty($ids)) {
|
||||
list($deletesql, $deleteparams) = $DB->get_in_or_equal($ids);
|
||||
$deletesql = 'id ' . $deletesql;
|
||||
$DB->delete_records_select('course_modules_viewed', $deletesql, $deleteparams);
|
||||
}
|
||||
|
||||
$DB->delete_records('course_completion_crit_compl', $params);
|
||||
$DB->delete_records('course_completions', $params);
|
||||
}
|
||||
@ -273,6 +301,7 @@ class provider implements
|
||||
// Only delete the record for course modules completion.
|
||||
$sql = "coursemoduleid = :coursemoduleid AND userid {$useridsql}";
|
||||
$DB->delete_records_select('course_modules_completion', $sql, $params);
|
||||
$DB->delete_records_select('course_modules_viewed', $sql, $params);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -292,6 +321,19 @@ class provider implements
|
||||
$DB->delete_records_select('course_modules_completion', $deletesql, $deleteparams);
|
||||
}
|
||||
|
||||
// Find records relating to course modules.
|
||||
$sql = "SELECT cmv.id
|
||||
FROM {course_completion_criteria} ccc
|
||||
JOIN {course_modules_viewed} cmv ON ccc.moduleinstance = cmv.coursemoduleid
|
||||
WHERE ccc.course = :course AND cmv.userid {$useridsql}";
|
||||
$recordids = $DB->get_records_sql($sql, $params);
|
||||
$ids = array_keys($recordids);
|
||||
if (!empty($ids)) {
|
||||
list($deletesql, $deleteparams) = $DB->get_in_or_equal($ids);
|
||||
$deletesql = 'id ' . $deletesql;
|
||||
$DB->delete_records_select('course_modules_viewed', $deletesql, $deleteparams);
|
||||
}
|
||||
|
||||
$sql = "course = :course AND userid {$useridsql}";
|
||||
$DB->delete_records_select('course_completion_crit_compl', $sql, $params);
|
||||
$DB->delete_records_select('course_completions', $sql, $params);
|
||||
|
57
completion/tests/behat/backup_restore_completion.feature
Normal file
57
completion/tests/behat/backup_restore_completion.feature
Normal file
@ -0,0 +1,57 @@
|
||||
@core @core_completion
|
||||
Feature: Backup and restore the activity with the completion
|
||||
|
||||
Background:
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname | category | enablecompletion |
|
||||
| Course 1 | C1 | 0 | 1 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| student1 | Student | First | student1@example.com |
|
||||
| student2 | Student | Second | student2@example.com |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| student1 | C1 | student |
|
||||
| student2 | C1 | student |
|
||||
And the following "activity" exists:
|
||||
| activity | assign |
|
||||
| course | C1 |
|
||||
| idnumber | a1 |
|
||||
| name | Test assignment name |
|
||||
| intro | Submit your online text |
|
||||
| assignsubmission_onlinetext_enabled | 1 |
|
||||
| assignsubmission_file_enabled | 0 |
|
||||
| completion | 2 |
|
||||
| completionview | 1 |
|
||||
| completionusegrade | 1 |
|
||||
| gradepass | 50 |
|
||||
| completionpassgrade | 1 |
|
||||
And I am on the "Test assignment name" "assign activity" page logged in as student1
|
||||
And I log out
|
||||
|
||||
@javascript @_file_upload
|
||||
Scenario: Restore the legacy assignment with completion condition.
|
||||
Given I am on the "Course 1" "restore" page logged in as "admin"
|
||||
And I press "Manage backup files"
|
||||
And I upload "completion/tests/fixtures/legacy_course_completion.mbz" file to "Files" filemanager
|
||||
And I press "Save changes"
|
||||
And I restore "legacy_course_completion.mbz" backup into a new course using this options:
|
||||
| Schema | Course name | Course 2 |
|
||||
| Schema | Course short name | C2 |
|
||||
When I am on the "Course 2" course page logged in as student1
|
||||
Then the "View" completion condition of "Test assignment name" is displayed as "done"
|
||||
And I am on the "Course 2" course page logged in as student2
|
||||
And the "View" completion condition of "Test assignment name" is displayed as "todo"
|
||||
|
||||
@javascript @_file_upload
|
||||
Scenario: Backup and restore the assignment with the viewed and not-viewed completion condition
|
||||
Given I am on the "Course 1" course page logged in as admin
|
||||
And I backup "Course 1" course using this options:
|
||||
| Confirmation | Filename | test_backup.mbz |
|
||||
And I restore "test_backup.mbz" backup into a new course using this options:
|
||||
| Schema | Course name | Course 2 |
|
||||
| Schema | Course short name | C2 |
|
||||
When I am on the "Course 2" course page logged in as student1
|
||||
Then the "View" completion condition of "Test assignment name" is displayed as "done"
|
||||
And I am on the "Course 2" course page logged in as student2
|
||||
And the "View" completion condition of "Test assignment name" is displayed as "todo"
|
@ -67,3 +67,25 @@ Feature: Students will be marked as completed and pass/fail
|
||||
And the "View" completion condition of "Test assignment name" is displayed as "todo"
|
||||
And the "Receive a grade" completion condition of "Test assignment name" is displayed as "done"
|
||||
And the "Receive a passing grade" completion condition of "Test assignment name" is displayed as "failed"
|
||||
|
||||
@javascript
|
||||
Scenario: Keep current view completion condition when the teacher does the action 'Unlock completion settings'.
|
||||
Given I am on the "Course 1" course page logged in as teacher1
|
||||
And I navigate to "View > Grader report" in the course gradebook
|
||||
And I turn editing mode on
|
||||
And I give the grade "21" to the user "Student First" for the grade item "Test assignment name"
|
||||
And I give the grade "50" to the user "Student Second" for the grade item "Test assignment name"
|
||||
And I press "Save changes"
|
||||
And I am on the "Test assignment name" "assign activity" page logged in as teacher1
|
||||
And I navigate to "Settings" in current page administration
|
||||
And I expand all fieldsets
|
||||
And I press "Unlock completion settings"
|
||||
And I expand all fieldsets
|
||||
And I should see "Completion options unlocked"
|
||||
And I click on "Save and display" "button"
|
||||
And I log out
|
||||
When I am on the "Course 1" course page logged in as student1
|
||||
Then the "View" completion condition of "Test assignment name" is displayed as "done"
|
||||
And I log out
|
||||
When I am on the "Course 1" course page logged in as student2
|
||||
Then the "View" completion condition of "Test assignment name" is displayed as "done"
|
||||
|
BIN
completion/tests/fixtures/legacy_course_completion.mbz
vendored
Normal file
BIN
completion/tests/fixtures/legacy_course_completion.mbz
vendored
Normal file
Binary file not shown.
@ -933,6 +933,7 @@ function course_delete_module($cmid, $async = false) {
|
||||
// features are not turned on, in case they were turned on previously (these will be
|
||||
// very quick on an empty table).
|
||||
$DB->delete_records('course_modules_completion', array('coursemoduleid' => $cm->id));
|
||||
$DB->delete_records('course_modules_viewed', ['coursemoduleid' => $cm->id]);
|
||||
$DB->delete_records('course_completion_criteria', array('moduleinstance' => $cm->id,
|
||||
'course' => $cm->course,
|
||||
'criteriatype' => COMPLETION_CRITERIA_TYPE_ACTIVITY));
|
||||
|
@ -208,6 +208,7 @@ $string['privacy:metadata:timecompleted'] = 'The time that the course was comple
|
||||
$string['privacy:metadata:timeenrolled'] = 'The time that the user was enrolled in the course';
|
||||
$string['privacy:metadata:timemodified'] = 'The time that the activity completion was modified';
|
||||
$string['privacy:metadata:timestarted'] = 'The time the course was started.';
|
||||
$string['privacy:metadata:timecreated'] = 'The time that the activity completion was created';
|
||||
$string['privacy:metadata:viewed'] = 'If the activity was viewed';
|
||||
$string['privacy:metadata:userid'] = 'The user ID of the person with course and activity completion data';
|
||||
$string['privacy:metadata:unenroled'] = 'If the user has been unenrolled from the course';
|
||||
|
@ -1091,13 +1091,16 @@ class completion_info {
|
||||
// If we're not caching the completion data, then just fetch the completion data for the user in this course module.
|
||||
if ($usecache && $wholecourse) {
|
||||
// Get whole course data for cache.
|
||||
$alldatabycmc = $DB->get_records_sql("SELECT cm.id AS cmid, cmc.*
|
||||
$alldatabycmc = $DB->get_records_sql("SELECT cm.id AS cmid, cmc.*,
|
||||
CASE WHEN cmv.id IS NULL THEN 0 ELSE 1 END AS viewed
|
||||
FROM {course_modules} cm
|
||||
LEFT JOIN {course_modules_completion} cmc ON cmc.coursemoduleid = cm.id
|
||||
AND cmc.userid = ?
|
||||
LEFT JOIN {course_modules_completion} cmc
|
||||
ON cmc.coursemoduleid = cm.id AND cmc.userid = ?
|
||||
LEFT JOIN {course_modules_viewed} cmv
|
||||
ON cmv.coursemoduleid = cm.id AND cmv.userid = ?
|
||||
INNER JOIN {modules} m ON m.id = cm.module
|
||||
WHERE m.visible = 1 AND cm.course = ?", [$userid, $this->course->id]);
|
||||
|
||||
WHERE m.visible = 1 AND cm.course = ?",
|
||||
[$userid, $userid, $this->course->id]);
|
||||
$cminfos = get_fast_modinfo($cm->course, $userid)->get_cms();
|
||||
|
||||
// Reindex by course module id.
|
||||
@ -1125,14 +1128,7 @@ class completion_info {
|
||||
$data = $cacheddata[$cminfo->id];
|
||||
} else {
|
||||
// Get single record
|
||||
$data = $DB->get_record('course_modules_completion', array('coursemoduleid' => $cminfo->id, 'userid' => $userid));
|
||||
if ($data) {
|
||||
$data = (array)$data;
|
||||
} else {
|
||||
// Row not present counts as 'not complete'.
|
||||
$data = $defaultdata;
|
||||
}
|
||||
|
||||
$data = $this->get_completion_data($cminfo->id, $userid, $defaultdata);
|
||||
// Put in cache.
|
||||
$cacheddata[$cminfo->id] = $data;
|
||||
}
|
||||
@ -1188,10 +1184,8 @@ class completion_info {
|
||||
// If view is required, try and fetch from the db. In some cases, cache can be invalid.
|
||||
if ($cm->completionview == COMPLETION_VIEW_REQUIRED) {
|
||||
$data['viewed'] = COMPLETION_INCOMPLETE;
|
||||
$record = $DB->get_record('course_modules_completion', array('coursemoduleid' => $cm->id, 'userid' => $userid));
|
||||
if ($record) {
|
||||
$data['viewed'] = ($record->viewed == COMPLETION_VIEWED ? COMPLETION_COMPLETE : COMPLETION_INCOMPLETE);
|
||||
}
|
||||
$record = $DB->record_exists('course_modules_viewed', ['coursemoduleid' => $cm->id, 'userid' => $userid]);
|
||||
$data['viewed'] = $record ? COMPLETION_COMPLETE : COMPLETION_INCOMPLETE;
|
||||
}
|
||||
|
||||
return $data;
|
||||
@ -1265,6 +1259,19 @@ class completion_info {
|
||||
// Has real (nonzero) id meaning that a database row exists, update
|
||||
$DB->update_record('course_modules_completion', $data);
|
||||
}
|
||||
$dataview = new stdClass();
|
||||
$dataview->coursemoduleid = $data->coursemoduleid;
|
||||
$dataview->userid = $data->userid;
|
||||
$dataview->id = $DB->get_field('course_modules_viewed', 'id',
|
||||
['coursemoduleid' => $dataview->coursemoduleid, 'userid' => $dataview->userid]);
|
||||
if (!$data->viewed && $dataview->id) {
|
||||
$DB->delete_records('course_modules_viewed', ['id' => $dataview->id]);
|
||||
}
|
||||
|
||||
if (!$dataview->id && $data->viewed) {
|
||||
$dataview->timecreated = time();
|
||||
$dataview->id = $DB->insert_record('course_modules_viewed', $dataview);
|
||||
}
|
||||
$transaction->allow_commit();
|
||||
|
||||
$cmcontext = context_module::instance($data->coursemoduleid);
|
||||
@ -1602,6 +1609,49 @@ class completion_info {
|
||||
throw new moodle_exception('err_system','completion',
|
||||
$CFG->wwwroot.'/course/view.php?id='.$this->course->id,null,$error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get completion data include viewed field.
|
||||
*
|
||||
* @param int $coursemoduleid The course module id.
|
||||
* @param int $userid The User ID.
|
||||
* @param array $defaultdata Default data completion.
|
||||
* @return array Data completion retrieved.
|
||||
*/
|
||||
public function get_completion_data(int $coursemoduleid, int $userid, array $defaultdata): array {
|
||||
global $DB;
|
||||
|
||||
// MySQL doesn't support FULL JOIN syntax, so we use UNION in the below SQL to help MySQL.
|
||||
$sql = "SELECT cmc.*, cmv.coursemoduleid as cmvcoursemoduleid, cmv.userid as cmvuserid
|
||||
FROM {course_modules_completion} cmc
|
||||
LEFT JOIN {course_modules_viewed} cmv ON cmc.coursemoduleid = cmv.coursemoduleid AND cmc.userid = cmv.userid
|
||||
WHERE cmc.coursemoduleid = :cmccoursemoduleid AND cmc.userid = :cmcuserid
|
||||
UNION
|
||||
SELECT cmc2.*, cmv2.coursemoduleid as cmvcoursemoduleid, cmv2.userid as cmvuserid
|
||||
FROM {course_modules_completion} cmc2
|
||||
RIGHT JOIN {course_modules_viewed} cmv2
|
||||
ON cmc2.coursemoduleid = cmv2.coursemoduleid AND cmc2.userid = cmv2.userid
|
||||
WHERE cmv2.coursemoduleid = :cmvcoursemoduleid AND cmv2.userid = :cmvuserid";
|
||||
|
||||
$data = $DB->get_record_sql($sql, ['cmccoursemoduleid' => $coursemoduleid, 'cmcuserid' => $userid,
|
||||
'cmvcoursemoduleid' => $coursemoduleid, 'cmvuserid' => $userid]);
|
||||
|
||||
if (!$data) {
|
||||
$data = $defaultdata;
|
||||
} else {
|
||||
if (empty($data->coursemoduleid) && empty($data->userid)) {
|
||||
$data->coursemoduleid = $data->cmvcoursemoduleid;
|
||||
$data->userid = $data->cmvuserid;
|
||||
}
|
||||
unset($data->cmvcoursemoduleid);
|
||||
unset($data->cmvuserid);
|
||||
|
||||
// When reseting all state in the completion, we need to keep current view state.
|
||||
$data->viewed = 1;
|
||||
}
|
||||
|
||||
return (array)$data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<XMLDB PATH="lib/db" VERSION="20220825" COMMENT="XMLDB file for core Moodle tables"
|
||||
<XMLDB PATH="lib/db" VERSION="20221013" COMMENT="XMLDB file for core Moodle tables"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
|
||||
>
|
||||
@ -162,7 +162,7 @@
|
||||
<FIELD NAME="course" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="criteriatype" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Type of criteria"/>
|
||||
<FIELD NAME="module" TYPE="char" LENGTH="100" NOTNULL="false" SEQUENCE="false" COMMENT="Type of module (if using module criteria type)"/>
|
||||
<FIELD NAME="moduleinstance" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Module instance id (if using module criteria type)"/>
|
||||
<FIELD NAME="moduleinstance" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Course module id (if using module criteria type)"/>
|
||||
<FIELD NAME="courseinstance" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Course instance id (if using course criteria type)"/>
|
||||
<FIELD NAME="enrolperiod" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Number of days after enrolment the course is completed (if using enrolperiod criteria type)"/>
|
||||
<FIELD NAME="timeend" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Timestamp of the date for course completion (if using date criteria type)"/>
|
||||
@ -331,7 +331,6 @@
|
||||
<FIELD NAME="coursemoduleid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Activity that has been completed (or not)."/>
|
||||
<FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="ID of user who has (or hasn't) completed the activity."/>
|
||||
<FIELD NAME="completionstate" TYPE="int" LENGTH="1" NOTNULL="true" SEQUENCE="false" COMMENT="Whether or not the user has completed the activity. Available states: 0 = not completed [if there's no row in this table, that also counts as 0] 1 = completed 2 = completed, show passed 3 = completed, show failed"/>
|
||||
<FIELD NAME="viewed" TYPE="int" LENGTH="1" NOTNULL="false" SEQUENCE="false" COMMENT="Tracks whether or not this activity has been viewed. NULL = we are not tracking viewed for this activity 0 = not viewed 1 = viewed"/>
|
||||
<FIELD NAME="overrideby" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Tracks whether this completion state has been set manually to override a previous state."/>
|
||||
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Time at which the completion state last changed."/>
|
||||
</FIELDS>
|
||||
@ -4745,5 +4744,20 @@
|
||||
<INDEX NAME="adminpresetapplyid" UNIQUE="false" FIELDS="adminpresetapplyid"/>
|
||||
</INDEXES>
|
||||
</TABLE>
|
||||
<TABLE NAME="course_modules_viewed" COMMENT="Tracks the completion viewed (viewed with cmid/userid and otherwise no row) of each user on each activity.">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="coursemoduleid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Activity that has been viewed (or not)."/>
|
||||
<FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="ID of user who has (or hasn't) viewed the activity."/>
|
||||
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Time at which the completion viewed created."/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
</KEYS>
|
||||
<INDEXES>
|
||||
<INDEX NAME="coursemoduleid" UNIQUE="false" FIELDS="coursemoduleid" COMMENT="For quick access via course-module (e.g. when displaying course module settings page and we need to determine whether anyone has completed it)."/>
|
||||
<INDEX NAME="userid-coursemoduleid" UNIQUE="true" FIELDS="userid, coursemoduleid"/>
|
||||
</INDEXES>
|
||||
</TABLE>
|
||||
</TABLES>
|
||||
</XMLDB>
|
||||
|
@ -2925,5 +2925,60 @@ privatefiles,moodle|/user/files.php';
|
||||
upgrade_main_savepoint(true, 2022092200.01);
|
||||
}
|
||||
|
||||
if ($oldversion < 2022101300.00) {
|
||||
// Define table to store completion viewed.
|
||||
$table = new xmldb_table('course_modules_viewed');
|
||||
|
||||
// Adding fields to table course_modules_viewed.
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table->add_field('coursemoduleid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null, 'id');
|
||||
$table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null, 'coursemoduleid');
|
||||
$table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0', 'userid');
|
||||
|
||||
// Adding keys to table course_modules_viewed.
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
|
||||
|
||||
// Adding indexes to table course_modules_viewed.
|
||||
$table->add_index('coursemoduleid', XMLDB_INDEX_NOTUNIQUE, ['coursemoduleid']);
|
||||
$table->add_index('userid-coursemoduleid', XMLDB_INDEX_UNIQUE, ['userid', 'coursemoduleid']);
|
||||
|
||||
if (!$dbman->table_exists($table)) {
|
||||
$dbman->create_table($table);
|
||||
}
|
||||
|
||||
// Main savepoint reached.
|
||||
upgrade_main_savepoint(true, 2022101300.00);
|
||||
}
|
||||
|
||||
if ($oldversion < 2022101300.01) {
|
||||
// Add legacy data to the new table.
|
||||
$transaction = $DB->start_delegated_transaction();
|
||||
upgrade_set_timeout(3600);
|
||||
$sql = "INSERT INTO {course_modules_viewed}
|
||||
(userid, coursemoduleid, timecreated)
|
||||
SELECT userid, coursemoduleid, timemodified
|
||||
FROM {course_modules_completion}
|
||||
WHERE viewed = 1";
|
||||
$DB->execute($sql);
|
||||
$transaction->allow_commit();
|
||||
|
||||
// Main savepoint reached.
|
||||
upgrade_main_savepoint(true, 2022101300.01);
|
||||
}
|
||||
|
||||
if ($oldversion < 2022101300.02) {
|
||||
// Define field viewed to be dropped from course_modules_completion.
|
||||
$table = new xmldb_table('course_modules_completion');
|
||||
$field = new xmldb_field('viewed');
|
||||
|
||||
// Conditionally launch drop field viewed.
|
||||
if ($dbman->field_exists($table, $field)) {
|
||||
$dbman->drop_field($table, $field);
|
||||
}
|
||||
|
||||
// Main savepoint reached.
|
||||
upgrade_main_savepoint(true, 2022101300.02);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -5208,6 +5208,7 @@ function remove_course_contents($courseid, $showfeedback = true, array $options
|
||||
// Delete cm and its context - orphaned contexts are purged in cron in case of any race condition.
|
||||
context_helper::delete_instance(CONTEXT_MODULE, $cm->id);
|
||||
$DB->delete_records('course_modules_completion', ['coursemoduleid' => $cm->id]);
|
||||
$DB->delete_records('course_modules_viewed', ['coursemoduleid' => $cm->id]);
|
||||
$DB->delete_records('course_modules', array('id' => $cm->id));
|
||||
rebuild_course_cache($cm->course, true);
|
||||
}
|
||||
@ -5231,6 +5232,8 @@ function remove_course_contents($courseid, $showfeedback = true, array $options
|
||||
// features are not enabled now, in case they were enabled previously.
|
||||
$DB->delete_records_subquery('course_modules_completion', 'coursemoduleid', 'id',
|
||||
'SELECT id from {course_modules} WHERE course = ?', [$courseid]);
|
||||
$DB->delete_records_subquery('course_modules_viewed', 'coursemoduleid', 'id',
|
||||
'SELECT id from {course_modules} WHERE course = ?', [$courseid]);
|
||||
|
||||
// Remove course-module data that has not been removed in modules' _delete_instance callbacks.
|
||||
$cms = $DB->get_records('course_modules', array('course' => $course->id));
|
||||
|
@ -767,11 +767,16 @@ class completionlib_test extends advanced_testcase {
|
||||
'coursemoduleid' => $cm->id,
|
||||
'userid' => $user->id,
|
||||
'completionstate' => $completion,
|
||||
'viewed' => 0,
|
||||
'overrideby' => null,
|
||||
'timemodified' => 0,
|
||||
];
|
||||
$cmcompletionviewrecord = (object)[
|
||||
'coursemoduleid' => $cm->id,
|
||||
'userid' => $user->id,
|
||||
'timecreated' => 0,
|
||||
];
|
||||
$DB->insert_record('course_modules_completion', $cmcompletionrecord);
|
||||
$DB->insert_record('course_modules_viewed', $cmcompletionviewrecord);
|
||||
}
|
||||
|
||||
// Whether we expect for the returned completion data to be stored in the cache.
|
||||
@ -832,11 +837,16 @@ class completionlib_test extends advanced_testcase {
|
||||
'coursemoduleid' => $cm->id,
|
||||
'userid' => $this->user->id,
|
||||
'completionstate' => COMPLETION_NOT_VIEWED,
|
||||
'viewed' => 0,
|
||||
'overrideby' => null,
|
||||
'timemodified' => 0,
|
||||
];
|
||||
$cmcompletionviewrecord = (object)[
|
||||
'coursemoduleid' => $cm->id,
|
||||
'userid' => $this->user->id,
|
||||
'timecreated' => 0,
|
||||
];
|
||||
$DB->insert_record('course_modules_completion', $cmcompletionrecord);
|
||||
$DB->insert_record('course_modules_viewed', $cmcompletionviewrecord);
|
||||
|
||||
// Mock other completion data.
|
||||
$completioninfo = new completion_info($this->course);
|
||||
@ -856,7 +866,6 @@ class completionlib_test extends advanced_testcase {
|
||||
|
||||
$this->assertEquals($testcm->id, $result->coursemoduleid);
|
||||
$this->assertEquals($this->user->id, $result->userid);
|
||||
$this->assertEquals(0, $result->viewed);
|
||||
|
||||
$results[$testcm->id] = $result;
|
||||
}
|
||||
@ -872,6 +881,59 @@ class completionlib_test extends advanced_testcase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for get_completion_data().
|
||||
*
|
||||
* @covers ::get_completion_data
|
||||
*/
|
||||
public function test_get_completion_data() {
|
||||
$this->setup_data();
|
||||
$choicegenerator = $this->getDataGenerator()->get_plugin_generator('mod_choice');
|
||||
$choice = $choicegenerator->create_instance([
|
||||
'course' => $this->course->id,
|
||||
'completion' => COMPLETION_TRACKING_AUTOMATIC,
|
||||
'completionview' => true,
|
||||
'completionsubmit' => true,
|
||||
]);
|
||||
$cm = get_coursemodule_from_instance('choice', $choice->id);
|
||||
|
||||
// Mock other completion data.
|
||||
$completioninfo = new completion_info($this->course);
|
||||
// Default data to return when no completion data is found.
|
||||
$defaultdata = [
|
||||
'id' => 0,
|
||||
'coursemoduleid' => $cm->id,
|
||||
'userid' => $this->user->id,
|
||||
'completionstate' => 0,
|
||||
'viewed' => 0,
|
||||
'overrideby' => null,
|
||||
'timemodified' => 0,
|
||||
];
|
||||
|
||||
$completiondatabeforeview = $completioninfo->get_completion_data($cm->id, $this->user->id, $defaultdata);
|
||||
$this->assertTrue(array_key_exists('viewed', $completiondatabeforeview));
|
||||
$this->assertTrue(array_key_exists('coursemoduleid', $completiondatabeforeview));
|
||||
$this->assertEquals(0, $completiondatabeforeview['viewed']);
|
||||
$this->assertEquals($cm->id, $completiondatabeforeview['coursemoduleid']);
|
||||
|
||||
// Set viewed.
|
||||
$completioninfo->set_module_viewed($cm, $this->user->id);
|
||||
|
||||
$completiondata = $completioninfo->get_completion_data($cm->id, $this->user->id, $defaultdata);
|
||||
$this->assertTrue(array_key_exists('viewed', $completiondata));
|
||||
$this->assertTrue(array_key_exists('coursemoduleid', $completiondata));
|
||||
$this->assertEquals(1, $completiondata['viewed']);
|
||||
$this->assertEquals($cm->id, $completiondatabeforeview['coursemoduleid']);
|
||||
|
||||
$completioninfo->reset_all_state($cm);
|
||||
|
||||
$completiondataafterreset = $completioninfo->get_completion_data($cm->id, $this->user->id, $defaultdata);
|
||||
$this->assertTrue(array_key_exists('viewed', $completiondataafterreset));
|
||||
$this->assertTrue(array_key_exists('coursemoduleid', $completiondataafterreset));
|
||||
$this->assertEquals(1, $completiondataafterreset['viewed']);
|
||||
$this->assertEquals($cm->id, $completiondatabeforeview['coursemoduleid']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for completion_info::get_other_cm_completion_data().
|
||||
*
|
||||
|
@ -29,7 +29,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$version = 2022101100.00; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
$version = 2022101400.00; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
// RR = release increments - 00 in DEV branches.
|
||||
// .XX = incremental changes.
|
||||
$release = '4.1dev+ (Build: 20221011)'; // Human-friendly version name
|
||||
|
Loading…
x
Reference in New Issue
Block a user