Commit 90acd8d3 authored by Peter's avatar Peter Committed by Peter Dias
Browse files

MDL-52206 core: Add completion criteria to course_modules

parent 66313774
......@@ -273,7 +273,8 @@ class backup_module_structure_step extends backup_structure_step {
'modulename', 'sectionid', 'sectionnumber', 'idnumber',
'added', 'score', 'indent', 'visible', 'visibleoncoursepage',
'visibleold', 'groupmode', 'groupingid',
'completion', 'completiongradeitemnumber', 'completionview', 'completionexpected',
'completion', 'completiongradeitemnumber', 'completionpassgrade',
'completionview', 'completionexpected',
'availability', 'showdescription'));
$tags = new backup_nested_element('tags');
......
......@@ -383,6 +383,8 @@ class manager {
$data['completiongradeitemnumber'] = !empty($data['completionusegrade']) ? 0 : null;
unset($data['completionusegrade']);
} else {
// Completion grade item number is classified in mod_edit forms as 'use grade'.
$data['completionusegrade'] = is_null($cm->completiongradeitemnumber) ? 0 : 1;
$data['completiongradeitemnumber'] = $cm->completiongradeitemnumber;
}
......@@ -422,7 +424,8 @@ class manager {
'completion' => COMPLETION_DISABLED,
'completionview' => COMPLETION_VIEW_NOT_REQUIRED,
'completionexpected' => 0,
'completionusegrade' => 0
'completionusegrade' => 0,
'completionpassgrade' => 0
];
$data = (array)$data;
......@@ -479,7 +482,7 @@ class manager {
public static function get_default_completion($course, $module, $flatten = true) {
global $DB, $CFG;
if ($data = $DB->get_record('course_completion_defaults', ['course' => $course->id, 'module' => $module->id],
'completion, completionview, completionexpected, completionusegrade, customrules')) {
'completion, completionview, completionexpected, completionusegrade, completionpassgrade, customrules')) {
if ($data->customrules && ($customrules = @json_decode($data->customrules, true))) {
if ($flatten) {
foreach ($customrules as $key => $value) {
......
......@@ -122,6 +122,15 @@ class core_completion_externallib_testcase extends externallib_advanced_testcase
['course' => $course->id],
['availability' => $availability],
);
$assignautocompletion = $this->getDataGenerator()->create_module('assign',
['course' => $course->id], [
'showdescription' => true,
'completionview' => 1,
'completion' => COMPLETION_TRACKING_AUTOMATIC,
'completiongradeitemnumber' => 1,
'completionpassgrade' => 1,
],
);
$page = $this->getDataGenerator()->create_module('page', array('course' => $course->id),
array('completion' => 1, 'visible' => 0));
......@@ -151,11 +160,11 @@ class core_completion_externallib_testcase extends externallib_advanced_testcase
$result = external_api::clean_returnvalue(
core_completion_external::get_activities_completion_status_returns(), $result);
// We added 5 activities, but only 4 with completion enabled and one of those is hidden.
$numberofactivities = 5;
// We added 6 activities, but only 4 with completion enabled and one of those is hidden.
$numberofactivities = 6;
$numberofhidden = 1;
$numberofcompletions = $numberofactivities - $numberofhidden;
$numberofstatusstudent = 3;
$numberofstatusstudent = 4;
$this->assertCount($numberofstatusstudent, $result['statuses']);
......@@ -186,6 +195,26 @@ class core_completion_externallib_testcase extends externallib_advanced_testcase
$this->assertEquals('completionview', $details[0]['rulename']);
$this->assertEquals(0, $details[0]['rulevalue']['status']);
} else if ($status['cmid'] == $assignautocompletion->cmid) {
$activitiesfound++;
$this->assertEquals(COMPLETION_INCOMPLETE, $status['state']);
$this->assertEquals(COMPLETION_TRACKING_AUTOMATIC, $status['tracking']);
$this->assertFalse($status['valueused']);
$this->assertTrue($status['hascompletion']);
$this->assertTrue($status['isautomatic']);
$this->assertTrue($status['istrackeduser']);
$this->assertTrue($status['uservisible']);
$details = $status['details'];
$this->assertCount(3, $details);
$expecteddetails = [
'completionview',
'completionusegrade',
'completionpassgrade',
];
foreach ($expecteddetails as $index => $name) {
$this->assertEquals($name, $details[$index]['rulename']);
$this->assertEquals(0, $details[$index]['rulevalue']['status']);
}
} else if ($status['cmid'] == $data->cmid and $status['modname'] == 'data' and $status['instance'] == $data->id) {
$activitiesfound++;
$this->assertEquals(COMPLETION_INCOMPLETE, $status['state']);
......@@ -200,7 +229,7 @@ class core_completion_externallib_testcase extends externallib_advanced_testcase
$this->assertCount(0, $details);
}
}
$this->assertEquals(3, $activitiesfound);
$this->assertEquals(4, $activitiesfound);
// Teacher should see students status, they are in different groups but the teacher can access all groups.
$this->setUser($teacher);
......@@ -248,7 +277,7 @@ class core_completion_externallib_testcase extends externallib_advanced_testcase
$activitiesfound++;
$this->assertEquals(COMPLETION_COMPLETE, $status['state']);
$this->assertEquals(COMPLETION_TRACKING_MANUAL, $status['tracking']);
} else if ($status['cmid'] == $forumautocompletion->cmid) {
} else if (in_array($status['cmid'], [$forumautocompletion->cmid, $assignautocompletion->cmid])) {
$activitiesfound++;
$this->assertEquals(COMPLETION_INCOMPLETE, $status['state']);
$this->assertEquals(COMPLETION_TRACKING_AUTOMATIC, $status['tracking']);
......@@ -258,7 +287,7 @@ class core_completion_externallib_testcase extends externallib_advanced_testcase
$this->assertEquals(COMPLETION_TRACKING_MANUAL, $status['tracking']);
}
}
$this->assertEquals(4, $activitiesfound);
$this->assertEquals(5, $activitiesfound);
// Change teacher role capabilities (disable access all groups).
$context = context_course::instance($course->id);
......
......@@ -2867,6 +2867,7 @@ class core_course_external extends external_api {
'visibleoncoursepage' => new external_value(PARAM_INT, 'If visible on course page', VALUE_OPTIONAL),
'visibleold' => new external_value(PARAM_INT, 'Visible old', VALUE_OPTIONAL),
'completiongradeitemnumber' => new external_value(PARAM_INT, 'Completion grade item', VALUE_OPTIONAL),
'completionpassgrade' => new external_value(PARAM_INT, 'Completion pass grade setting', VALUE_OPTIONAL),
'completionview' => new external_value(PARAM_INT, 'Completion view setting', VALUE_OPTIONAL),
'completionexpected' => new external_value(PARAM_INT, 'Completion time expected', VALUE_OPTIONAL),
'showdescription' => new external_value(PARAM_INT, 'If the description is showed', VALUE_OPTIONAL),
......
......@@ -461,6 +461,7 @@ function get_array_of_activities($courseid) {
$mod[$seq]->extra = "";
$mod[$seq]->completiongradeitemnumber =
$rawmods[$seq]->completiongradeitemnumber;
$mod[$seq]->completionpassgrade = $rawmods[$seq]->completionpassgrade;
$mod[$seq]->completionview = $rawmods[$seq]->completionview;
$mod[$seq]->completionexpected = $rawmods[$seq]->completionexpected;
$mod[$seq]->showdescription = $rawmods[$seq]->showdescription;
......
......@@ -72,6 +72,7 @@ function add_moduleinfo($moduleinfo, $course, $mform = null) {
$completion = new completion_info($course);
if ($completion->is_enabled()) {
$newcm->completion = $moduleinfo->completion;
$newcm->completionpassgrade = $moduleinfo->completionpassgrade ?? 0;
if ($moduleinfo->completiongradeitemnumber === '') {
$newcm->completiongradeitemnumber = null;
} else {
......@@ -432,6 +433,8 @@ function set_moduleinfo_defaults($moduleinfo) {
if (isset($moduleinfo->completionusegrade) && $moduleinfo->completionusegrade) {
$moduleinfo->completiongradeitemnumber = 0;
} else if (!isset($moduleinfo->completiongradeitemnumber)) {
// If there is no gradeitemnumber set, make sure to disable completionpassgrade.
$moduleinfo->completionpassgrade = 0;
$moduleinfo->completiongradeitemnumber = null;
}
......@@ -544,6 +547,7 @@ function update_moduleinfo($cm, $moduleinfo, $course, $mform = null) {
// the activity may be locked; if so, these should not be updated.
if (!empty($moduleinfo->completionunlocked)) {
$cm->completion = $moduleinfo->completion;
$cm->completionpassgrade = $moduleinfo->completionpassgrade ?? 0;
if ($moduleinfo->completiongradeitemnumber === '') {
$cm->completiongradeitemnumber = null;
} else {
......@@ -712,6 +716,7 @@ function get_moduleinfo_data($cm, $course) {
$data->completionview = $cm->completionview;
$data->completionexpected = $cm->completionexpected;
$data->completionusegrade = is_null($cm->completiongradeitemnumber) ? 0 : 1;
$data->completionpassgrade = $cm->completionpassgrade;
$data->completiongradeitemnumber = $cm->completiongradeitemnumber;
$data->showdescription = $cm->showdescription;
$data->tags = core_tag_tag::get_item_tags_array('core', 'course_modules', $cm->id);
......
......@@ -372,6 +372,12 @@ abstract class moodleform_mod extends moodleform {
}
if ($mform->elementExists('completionpassgrade')) {
$mform->freeze('completionpassgrade');
// Has the completion pass grade completion criteria been set?
// If it has then we shouldn't change the gradepass field.
if ($mform->exportValue('completionpassgrade')) {
$mform->freeze('gradepass');
}
}
if ($mform->elementExists('completiongradeitemnumber')) {
$mform->freeze('completiongradeitemnumber');
......
......@@ -221,6 +221,7 @@ class core_course_courselib_testcase extends advanced_testcase {
$moduleinfo->completion = COMPLETION_TRACKING_AUTOMATIC;
$moduleinfo->completionview = COMPLETION_VIEW_REQUIRED;
$moduleinfo->completiongradeitemnumber = 1;
$moduleinfo->completionpassgrade = 0;
$moduleinfo->completionexpected = time() + (7 * 24 * 3600);
// Conditional activity.
......@@ -283,6 +284,7 @@ class core_course_courselib_testcase extends advanced_testcase {
$this->assertEquals($moduleinfo->completion, $dbcm->completion);
$this->assertEquals($moduleinfo->completionview, $dbcm->completionview);
$this->assertEquals($moduleinfo->completiongradeitemnumber, $dbcm->completiongradeitemnumber);
$this->assertEquals($moduleinfo->completionpassgrade, $dbcm->completionpassgrade);
$this->assertEquals($moduleinfo->completionexpected, $dbcm->completionexpected);
$this->assertEquals($moduleinfo->availability, $dbcm->availability);
$this->assertEquals($moduleinfo->showdescription, $dbcm->showdescription);
......@@ -504,6 +506,7 @@ class core_course_courselib_testcase extends advanced_testcase {
$moduleinfo->completion = COMPLETION_TRACKING_AUTOMATIC;
$moduleinfo->completionview = COMPLETION_VIEW_REQUIRED;
$moduleinfo->completiongradeitemnumber = 1;
$moduleinfo->completionpassgrade = 0;
$moduleinfo->completionexpected = time() + (7 * 24 * 3600);
$moduleinfo->completionunlocked = 1;
......@@ -561,6 +564,7 @@ class core_course_courselib_testcase extends advanced_testcase {
$this->assertEquals($moduleinfo->completion, $dbcm->completion);
$this->assertEquals($moduleinfo->completionview, $dbcm->completionview);
$this->assertEquals($moduleinfo->completiongradeitemnumber, $dbcm->completiongradeitemnumber);
$this->assertEquals($moduleinfo->completionpassgrade, $dbcm->completionpassgrade);
$this->assertEquals($moduleinfo->completionexpected, $dbcm->completionexpected);
$this->assertEquals($moduleinfo->availability, $dbcm->availability);
$this->assertEquals($moduleinfo->showdescription, $dbcm->showdescription);
......
......@@ -2306,14 +2306,17 @@ class externallib_test extends externallib_advanced_testcase {
$this->resetAfterTest(true);
$this->setAdminUser();
$course = self::getDataGenerator()->create_course();
$course = self::getDataGenerator()->create_course(['enablecompletion' => 1]);
$record = array(
'course' => $course->id,
'name' => 'First Assignment'
);
$options = array(
'idnumber' => 'ABC',
'visible' => 0
'visible' => 0,
'completion' => COMPLETION_TRACKING_AUTOMATIC,
'completiongradeitemnumber' => 0,
'completionpassgrade' => 1,
);
// Hidden activity.
$assign = self::getDataGenerator()->create_module('assign', $record, $options);
......@@ -2370,7 +2373,7 @@ class externallib_test extends externallib_advanced_testcase {
$this->assertCount(0, $result['warnings']);
// Test we retrieve all the fields.
$this->assertCount(28, $result['cm']);
$this->assertCount(29, $result['cm']);
$this->assertEquals($record['name'], $result['cm']['name']);
$this->assertEquals($options['idnumber'], $result['cm']['idnumber']);
$this->assertEquals(100, $result['cm']['grade']);
......@@ -2438,7 +2441,7 @@ class externallib_test extends externallib_advanced_testcase {
$this->assertCount(0, $result['warnings']);
// Test we retrieve all the fields.
$this->assertCount(26, $result['cm']);
$this->assertCount(27, $result['cm']);
$this->assertEquals($record['name'], $result['cm']['name']);
$this->assertEquals($record['grade'], $result['cm']['grade']);
$this->assertEquals($options['idnumber'], $result['cm']['idnumber']);
......
......@@ -112,6 +112,7 @@ class core_course_modlib_testcase extends advanced_testcase {
$expecteddata->completionview = $assigncm->completionview;
$expecteddata->completionexpected = $assigncm->completionexpected;
$expecteddata->completionusegrade = is_null($assigncm->completiongradeitemnumber) ? 0 : 1;
$expecteddata->completionpassgrade = $assigncm->completionpassgrade;
$expecteddata->completiongradeitemnumber = null;
$expecteddata->showdescription = $assigncm->showdescription;
$expecteddata->tags = core_tag_tag::get_item_tags_array('core', 'course_modules', $assigncm->id);
......@@ -138,6 +139,7 @@ class core_course_modlib_testcase extends advanced_testcase {
}
}
$expecteddata->gradepass = '0.00';
$expecteddata->completionpassgrade = $assigncm->completionpassgrade;
// Unset untestable.
unset($expecteddata->cmid);
......
......@@ -711,7 +711,16 @@ class completion_info {
// Check grade
if (!is_null($cminfo->completiongradeitemnumber)) {
$newstate = $this->get_grade_completion($cminfo, $userid);
if ($newstate == COMPLETION_INCOMPLETE) {
if ($cm->completionpassgrade) {
// If we are asking to use pass grade completion but haven't set it,
// then default to COMPLETION_COMPLETE_PASS.
if ($newstate == COMPLETION_COMPLETE) {
return COMPLETION_COMPLETE_PASS;
} else if ($newstate != COMPLETION_COMPLETE_PASS) {
// Mark as incomplete if there is no grade provided or the grade has failed.
$newstate = COMPLETION_INCOMPLETE;
}
} else if ($newstate == COMPLETION_INCOMPLETE) {
return COMPLETION_INCOMPLETE;
}
}
......
......@@ -2878,5 +2878,19 @@ function xmldb_main_upgrade($oldversion) {
upgrade_main_savepoint(true, 2021100300.01);
}
if ($oldversion < 2021100300.02) {
$table = new xmldb_table('course_modules');
// Adding new fields to table course_module table.
$field = new xmldb_field('completionpassgrade', XMLDB_TYPE_INTEGER, '1', null,
XMLDB_NOTNULL, null, '0', 'completionexpected');
// Conditionally launch create table for course_completion_defaults.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
upgrade_main_savepoint(true, 2021100300.02);
}
return true;
}
......@@ -970,6 +970,12 @@ class cm_info implements IteratorAggregate {
*/
private $completiongradeitemnumber;
/**
* 1 if pass grade completion is enabled, 0 otherwise - from course_modules table
* @var int
*/
private $completionpassgrade;
/**
* 1 if 'on view' completion is enabled, 0 otherwise - from course_modules table
* @var int
......@@ -1173,6 +1179,7 @@ class cm_info implements IteratorAggregate {
'completion' => false,
'completionexpected' => false,
'completiongradeitemnumber' => false,
'completionpassgrade' => false,
'completionview' => false,
'conditionscompletion' => false,
'conditionsfield' => false,
......@@ -1648,7 +1655,7 @@ class cm_info implements IteratorAggregate {
// Standard fields from table course_modules.
static $cmfields = array('id', 'course', 'module', 'instance', 'section', 'idnumber', 'added',
'score', 'indent', 'visible', 'visibleoncoursepage', 'visibleold', 'groupmode', 'groupingid',
'completion', 'completiongradeitemnumber', 'completionview', 'completionexpected',
'completion', 'completiongradeitemnumber', 'completionview', 'completionexpected', 'completionpassgrade',
'showdescription', 'availability', 'deletioninprogress');
foreach ($cmfields as $key) {
$cmrecord->$key = $this->$key;
......@@ -1867,6 +1874,7 @@ class cm_info implements IteratorAggregate {
// availability and completion fields, even if availability or completion
// are actually disabled
$this->completion = isset($mod->completion) ? $mod->completion : 0;
$this->completionpassgrade = isset($mod->completionpassgrade) ? $mod->completionpassgrade : 0;
$this->completiongradeitemnumber = isset($mod->completiongradeitemnumber)
? $mod->completiongradeitemnumber : null;
$this->completionview = isset($mod->completionview)
......
......@@ -176,7 +176,7 @@ abstract class testing_module_generator extends component_generator_base {
$easymergefields = array('section', 'added', 'score', 'indent',
'visible', 'visibleold', 'groupmode', 'groupingid',
'completion', 'completiongradeitemnumber', 'completionview', 'completionexpected',
'availability', 'showdescription');
'completionpassgrade', 'availability', 'showdescription');
foreach ($easymergefields as $key) {
if (isset($options[$key])) {
$moduleinfo->$key = $options[$key];
......@@ -195,6 +195,7 @@ abstract class testing_module_generator extends component_generator_base {
'completion' => 0,
'completionview' => 0,
'completionexpected' => 0,
'completionpassgrade' => 0,
'conditiongradegroup' => array(),
'conditionfieldgroup' => array(),
'conditioncompletiongroup' => array()
......
......@@ -258,6 +258,7 @@ class core_test_generator_testcase extends advanced_testcase {
'completion' => COMPLETION_TRACKING_AUTOMATIC, // "Show activity as complete when conditions are met."
'completionview' => 1, // "Student must view this activity to complete it"
'completionusegrade' => 1, // "Student must receive a grade to complete this activity"
'completionpassgrade' => 1, // "Student must receive a passing grade to complete this activity"
);
// Module supports FEATURE_RATE:
......@@ -327,6 +328,7 @@ class core_test_generator_testcase extends advanced_testcase {
$cm3 = $modinfo->cms[$m3->cmid];
$this->assertEquals($featurecompletionautomatic['completion'], $cm3->completion);
$this->assertEquals($featurecompletionautomatic['completionview'], $cm3->completionview);
$this->assertEquals($featurecompletionautomatic['completionpassgrade'], $cm3->completionpassgrade);
$this->assertEquals(0, $cm3->completiongradeitemnumber); // Zero instead of default null since 'completionusegrade' was set.
$gradingitem = grade_item::fetch(array('courseid'=>$course->id, 'itemtype'=>'mod', 'itemmodule' => 'assign', 'iteminstance' => $m3->id));
$this->assertEquals(0, $gradingitem->grademin);
......
......@@ -725,6 +725,7 @@ class core_completionlib_testcase extends advanced_testcase {
'completion' => COMPLETION_TRACKING_AUTOMATIC,
// Submission grade required.
'completiongradeitemnumber' => 0,
'completionpassgrade' => 1,
]);
$cmworkshop = cm_info::create(get_coursemodule_from_instance('workshop', $workshop->id));
......@@ -756,8 +757,10 @@ class core_completionlib_testcase extends advanced_testcase {
$workshopcompletiondata = $method->invoke($completioninfo, $cmworkshop, $user->id);
$this->assertArrayHasKey('completiongrade', $workshopcompletiondata);
$this->assertArrayHasKey('passgrade', $workshopcompletiondata);
$this->assertArrayNotHasKey('customcompletion', $workshopcompletiondata);
$this->assertEquals(COMPLETION_INCOMPLETE, $workshopcompletiondata['completiongrade']);
$this->assertEquals(COMPLETION_INCOMPLETE, $workshopcompletiondata['passgrade']);
// Check that fetching data for a module with no completion conditions does not provide any data.
$choice2completiondata = $method->invoke($completioninfo, $cmchoice2, $user->id);
......
......@@ -183,6 +183,7 @@ class modinfolib_test extends advanced_testcase {
$this->assertEquals($moduledb->indent, $cm->indent);
$this->assertEquals($moduledb->completion, $cm->completion);
$this->assertEquals($moduledb->completiongradeitemnumber, $cm->completiongradeitemnumber);
$this->assertEquals($moduledb->completionpassgrade, $cm->completionpassgrade);
$this->assertEquals($moduledb->completionview, $cm->completionview);
$this->assertEquals($moduledb->completionexpected, $cm->completionexpected);
$this->assertEquals($moduledb->showdescription, $cm->showdescription);
......
......@@ -275,6 +275,9 @@ class mod_scorm_mod_form extends moodleform_mod {
$this->standard_coursemodule_elements();
// A SCORM module should define this within itself and is not needed here.
$mform->removeElement('completionpassgrade');
// Buttons.
$this->add_action_buttons();
}
......
......@@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
$version = 2021100300.01; // YYYYMMDD = weekly release date of this DEV branch.
$version = 2021100300.02; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.
$release = '4.0dev (Build: 20211003)'; // Human-friendly version name
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment