Skip to content

Commit

Permalink
add logic to upgrade legacy courses to the current format
Browse files Browse the repository at this point in the history
  • Loading branch information
Glutamat42 committed Sep 16, 2024
1 parent 837e298 commit 22cf619
Show file tree
Hide file tree
Showing 2 changed files with 281 additions and 0 deletions.
119 changes: 119 additions & 0 deletions classes/local/upgrade/upgrade_3_2_0_to_4_0_0_completionlib.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<?php

namespace local_adler\local\upgrade;

use cm_info;
use dml_exception;
use local_adler\helpers;
use local_adler\local\db\moodle_core_repository;
use local_adler\local\exceptions\not_an_adler_course_exception;
use local_logging\logger;
use moodle_exception;

class upgrade_3_2_0_to_4_0_0_completionlib {
private int $course_id;
private logger $logger;
private moodle_core_repository $moodle_core_repository;
public function __construct(int $course_id) {
$this->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
]);
}
}
162 changes: 162 additions & 0 deletions tests/local/upgrade/upgrade_3_2_0_to_4_0_0_completionlib_test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
<?php /** @noinspection PhpIllegalPsrClassPathInspection */

global $CFG;

use local_adler\lib\adler_testcase;
use local_adler\local\backport\backport_cm_completion_details;
use local_adler\local\exceptions\not_an_adler_course_exception;
use local_adler\local\upgrade\upgrade_3_2_0_to_4_0_0_completionlib;

require_once($CFG->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);
}
}

0 comments on commit 22cf619

Please sign in to comment.