diff --git a/classes/local/upgrade/upgrade_3_2_0_to_4_0_0_completionlib.php b/classes/local/upgrade/upgrade_3_2_0_to_4_0_0_completionlib.php new file mode 100644 index 0000000..bd97fe0 --- /dev/null +++ b/classes/local/upgrade/upgrade_3_2_0_to_4_0_0_completionlib.php @@ -0,0 +1,119 @@ +logger = new logger('local_adler', self::class); + $this->course_id = $course_id; + $this->moodle_core_repository = new moodle_core_repository(); + } + + /** + * @throws not_an_adler_course_exception + * @throws moodle_exception + */ + public function execute(): void { + // Check if the course is an Adler course + if (!helpers::course_is_adler_course($this->course_id)) { + throw new not_an_adler_course_exception(); + } + + $this->upgrade_modules(); + $this->resetCourseCache(); + } + + /** + * @param cm_info $cm_info + * @return void + * @throws dml_exception + */ + private function upgrade_module(cm_info $cm_info): void { + if ($cm_info->completion == COMPLETION_TRACKING_MANUAL) { + if ($cm_info->modname == "h5pactivity") { + $this->upgrade_h5p_module($cm_info); + } else { + $this->upgrade_normal_module($cm_info); + } + } else { + $this->logger->warning('Completion for cm ' . $cm_info->id . ' is already set to auto, skipping'); + } + } + + /** + * @return void + * @throws moodle_exception + */ + private function resetCourseCache(): void { + purge_caches(['courses' => $this->course_id]); + get_fast_modinfo($this->course_id, 0, true); + } + + /** + * @return void + * @throws moodle_exception + */ + public function upgrade_modules(): void { + global $DB; + $cm_infos = get_fast_modinfo($this->course_id)->get_cms(); + + $transaction = $DB->start_delegated_transaction(); + foreach ($cm_infos as $cm_info) { + $this->upgrade_module($cm_info); + } + $transaction->allow_commit(); + } + + /** + * @param cm_info $cm_info + * @return void + * @throws dml_exception + */ + public function upgrade_h5p_module(cm_info $cm_info): void { + $grade_item = $this->moodle_core_repository->get_grade_item('h5pactivity', $cm_info->instance); + if ($grade_item) { + $this->logger->info('Setting completion for h5p cm ' . $cm_info->id . ' to "passing grade"'); + $this->set_completion_to_auto($cm_info, false); + $this->moodle_core_repository->update_grade_item_record($grade_item->id, [ + 'gradepass' => $grade_item->grademax + ]); + } else { + $this->logger->error('No grade item found for h5p cm ' . $cm_info->id); + } + } + + /** + * @param cm_info $cm_info + * @return void + * @throws dml_exception + */ + public function upgrade_normal_module(cm_info $cm_info): void { + $this->logger->info('Setting completion for cm ' . $cm_info->id . ' to view tracking'); + $this->set_completion_to_auto($cm_info, true); + } + + /** + * @param cm_info $cm_info + * @param bool $view if true: complete on view, if false: complete when achieving a passing grade + * @return void + * @throws dml_exception + */ + public function set_completion_to_auto(cm_info $cm_info, bool $view): void { + $this->moodle_core_repository->update_course_module_record($cm_info->id, [ + 'completion' => 2, + 'completionpassgrade' => $view ? 0 : 1, + 'completionview' => $view ? 1 : 0 + ]); + } +} \ No newline at end of file diff --git a/tests/local/upgrade/upgrade_3_2_0_to_4_0_0_completionlib_test.php b/tests/local/upgrade/upgrade_3_2_0_to_4_0_0_completionlib_test.php new file mode 100644 index 0000000..dc1f0d3 --- /dev/null +++ b/tests/local/upgrade/upgrade_3_2_0_to_4_0_0_completionlib_test.php @@ -0,0 +1,162 @@ +dirroot . '/local/adler/tests/lib/adler_testcase.php'); + +class upgrade_3_2_0_to_4_0_0_completionlib_test extends adler_testcase { + public function provide_execute_simple_learning_element_data(): array { + return [ + 'simple LE' => [ + 'module' => 'url' + ], + 'h5p LE' => [ + 'module' => 'h5pactivity' + ] + ]; + } + + /** + * @dataProvider provide_execute_simple_learning_element_data + */ + public function test_execute(string $module_type) { + global $DB; + + // create course + $course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]); + // make course an adler course + $this->getDataGenerator()->get_plugin_generator('local_adler')->create_adler_course_object($course->id); + + // create module + $user = $this->getDataGenerator()->create_user(); + $this->setUser($user); + $module = $this->create_legacy_module($module_type, $course->id); + + $cud = new upgrade_3_2_0_to_4_0_0_completionlib($course->id); + $cud->execute(); + + // check if completion is set to view tracking + $cm = get_fast_modinfo($course->id)->get_cm($module->cmid); + $this->assertEquals(COMPLETION_TRACKING_AUTOMATIC, $cm->completion); + if ($module_type == 'url') { + $this->assertEquals(0, $cm->completionpassgrade); + $this->assertEquals(1, $cm->completionview); + } else { + $this->assertEquals(1, $cm->completionpassgrade); + $this->assertEquals(0, $cm->completionview); + // check passing grade + $grade_max = $DB->get_field('grade_items', 'grademax', ['itemmodule' => $module_type, 'iteminstance' => $module->id]); + $pass_grade = $DB->get_field('grade_items', 'gradepass', ['itemmodule' => $module_type, 'iteminstance' => $module->id]); + $this->assertEquals($grade_max, $pass_grade); + } + } + + public function test_completion_already_auto() { + // create course + $course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]); + // make course an adler course + $this->getDataGenerator()->get_plugin_generator('local_adler')->create_adler_course_object($course->id); + + // create module + $module = $this->getDataGenerator()->create_module('url', [ + 'course' => $course->id, + 'completion' => COMPLETION_TRACKING_AUTOMATIC, + 'completeionview' => 0, + 'completionpassgrade' => 0 + ]); + + $cud = new upgrade_3_2_0_to_4_0_0_completionlib($course->id); + $cud->execute(); + + // check if completion is set to view tracking + $cm = get_fast_modinfo($course->id)->get_cm($module->cmid); + $this->assertEquals(COMPLETION_TRACKING_AUTOMATIC, $cm->completion); + $this->assertEquals(0, $cm->completionpassgrade); + $this->assertEquals(0, $cm->completionview); + } + + public function test_execute_not_adler_course() { + $course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]); + + $this->expectException(not_an_adler_course_exception::class); + + $cud = new upgrade_3_2_0_to_4_0_0_completionlib($course->id); + $cud->execute(); + } + + + + private function create_legacy_module(string $module_type, int $course_id) { + return $this->getDataGenerator()->get_plugin_generator('mod_' . $module_type)->create_instance([ + 'course' => $course_id, + 'completion' => COMPLETION_TRACKING_MANUAL, + 'completeionview' => 0, + 'completionpassgrade' => 0 + ]); + } + + public function test_with_user_attempt() { + // create course + $course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]); + // make course an adler course + $this->getDataGenerator()->get_plugin_generator('local_adler')->create_adler_course_object($course->id); + + // create module + $user = $this->getDataGenerator()->create_user(); + $this->setUser($user); + $module = $this->create_legacy_module('h5pactivity', $course->id); + + // mark as completed for user + $completion = new completion_info($course); + $completion->update_state(get_fast_modinfo($course)->get_cm($module->cmid), COMPLETION_COMPLETE, $user->id); + // verify element is completed for user + $h5p_completion_state = backport_cm_completion_details::get_instance( + get_fast_modinfo($course)->get_cm($module->cmid), + $user->id + ); + $this->assertTrue($h5p_completion_state->is_overall_complete(), 'Element is not completed before test'); + + $cud = new upgrade_3_2_0_to_4_0_0_completionlib($course->id); + $cud->execute(); + + // check if completion is set to view tracking + $cm = get_fast_modinfo($course->id)->get_cm($module->cmid); + $this->assertEquals(COMPLETION_TRACKING_AUTOMATIC, $cm->completion); + $this->assertEquals(1, $cm->completionpassgrade); + $this->assertEquals(0, $cm->completionview); + + // verify element is still completed + $h5p_completion_state = backport_cm_completion_details::get_instance( // has to be recreated to reflect completion state change + get_fast_modinfo($course)->get_cm($module->cmid), + $user->id + ); + $this->assertTrue($h5p_completion_state->is_overall_complete(), 'Element is not completed after test'); + + // create attempt + $this->create_h5p_attempt($module, $course->id, $user->id); + // verify element is still completed + $h5p_completion_state = backport_cm_completion_details::get_instance( // has to be recreated to reflect completion state change + get_fast_modinfo($course)->get_cm($module->cmid), + $user->id + ); + } + + private function create_h5p_attempt(stdClass $module, int $course_id, int $user_id) { + global $CFG; + $grade_item = grade_item::fetch([ + 'itemname' => $module->name, + 'gradetype' => GRADE_TYPE_VALUE, + 'courseid' => $course_id + ]); + $grade_data_class = new stdClass(); + $grade_data_class->userid = $user_id; + $grade_data_class->rawgrade = $grade_item->grademax; + require_once($CFG->dirroot . '/mod/h5pactivity/lib.php'); + h5pactivity_grade_item_update($module, $grade_data_class); + } +}