Skip to content

Commit

Permalink
MDL-40838 backup: restore enrolment methods without users
Browse files Browse the repository at this point in the history
  • Loading branch information
marinaglancy committed Feb 20, 2017
1 parent 9ec952f commit 92253b1
Show file tree
Hide file tree
Showing 10 changed files with 332 additions and 13 deletions.
5 changes: 5 additions & 0 deletions backup/backup.class.php
Expand Up @@ -125,6 +125,11 @@ abstract class backup implements checksumable {
const OPERATION_BACKUP ='backup'; // We are performing one backup
const OPERATION_RESTORE ='restore';// We are performing one restore

// Options for "Include enrolment methods" restore setting.
const ENROL_NEVER = 0;
const ENROL_WITHUSERS = 1;
const ENROL_ALWAYS = 2;

// Version and release (to keep CFG->backup_version (and release) updated automatically).
/**
* Usually same than major release version, this is used to mark important
Expand Down
1 change: 1 addition & 0 deletions backup/controller/restore_controller.class.php
Expand Up @@ -50,6 +50,7 @@ class restore_controller extends base_controller {
protected $precheck; // Results of the execution of restore prechecks

protected $info; // Information retrieved from backup contents
/** @var restore_plan */
protected $plan; // Restore execution plan

protected $execution; // inmediate/delayed
Expand Down
5 changes: 4 additions & 1 deletion backup/moodle2/restore_course_task.class.php
Expand Up @@ -78,7 +78,10 @@ public function build() {
// No need to do anything with enrolments.

} else if (!$this->get_setting_value('users') or $this->plan->get_mode() == backup::MODE_HUB) {
if ($this->get_target() == backup::TARGET_CURRENT_ADDING or $this->get_target() == backup::TARGET_EXISTING_ADDING) {
if ($this->get_setting_value('enrolments') == backup::ENROL_ALWAYS && $this->plan->get_mode() != backup::MODE_HUB) {
// Restore enrolment methods.
$this->add_step(new restore_enrolments_structure_step('course_enrolments', 'enrolments.xml'));
} else if ($this->get_target() == backup::TARGET_CURRENT_ADDING or $this->get_target() == backup::TARGET_EXISTING_ADDING) {
// Keep current enrolments unchanged.
} else {
// If no instances yet add default enrol methods the same way as when creating new course in UI.
Expand Down
26 changes: 20 additions & 6 deletions backup/moodle2/restore_root_task.class.php
Expand Up @@ -112,12 +112,26 @@ protected function define_settings() {
$users->get_ui()->set_changeable($changeable);
$this->add_setting($users);

$rootenrolmanual = new restore_users_setting('enrol_migratetomanual', base_setting::IS_BOOLEAN, false);
$rootenrolmanual->set_ui(new backup_setting_ui_checkbox($rootenrolmanual, get_string('rootenrolmanual', 'backup')));
$rootenrolmanual->get_ui()->set_changeable(enrol_is_enabled('manual'));
$rootenrolmanual->get_ui()->set_changeable($changeable);
$this->add_setting($rootenrolmanual);
$users->add_dependency($rootenrolmanual);
// Restore enrolment methods.
if ($changeable) {
$options = [
backup::ENROL_NEVER => get_string('rootsettingenrolments_never', 'backup'),
backup::ENROL_WITHUSERS => get_string('rootsettingenrolments_withusers', 'backup'),
backup::ENROL_ALWAYS => get_string('rootsettingenrolments_always', 'backup'),
];
$enroldefault = backup::ENROL_WITHUSERS;
} else {
// Users can not be restored, simplify the dropdown.
$options = [
backup::ENROL_NEVER => get_string('no'),
backup::ENROL_ALWAYS => get_string('yes')
];
$enroldefault = backup::ENROL_NEVER;
}
$enrolments = new restore_users_setting('enrolments', base_setting::IS_INTEGER, $enroldefault);
$enrolments->set_ui(new backup_setting_ui_select($enrolments, get_string('rootsettingenrolments', 'backup'),
$options));
$this->add_setting($enrolments);

// Define role_assignments (dependent of users)
$defaultvalue = false; // Safer default
Expand Down
22 changes: 17 additions & 5 deletions backup/moodle2/restore_stepslib.php
Expand Up @@ -2154,12 +2154,17 @@ protected function execute_condition() {

protected function define_structure() {

$enrol = new restore_path_element('enrol', '/enrolments/enrols/enrol');
$enrolment = new restore_path_element('enrolment', '/enrolments/enrols/enrol/user_enrolments/enrolment');
$userinfo = $this->get_setting_value('users');

$paths = [];
$paths[] = $enrol = new restore_path_element('enrol', '/enrolments/enrols/enrol');
if ($userinfo) {
$paths[] = new restore_path_element('enrolment', '/enrolments/enrols/enrol/user_enrolments/enrolment');
}
// Attach local plugin stucture to enrol element.
$this->add_plugin_structure('enrol', $enrol);

return array($enrol, $enrolment);
return $paths;
}

/**
Expand Down Expand Up @@ -2203,7 +2208,14 @@ public function process_enrol($data) {
$data->roleid = $this->get_mappingid('role', $data->roleid);
$data->courseid = $courserec->id;

if ($this->get_setting_value('enrol_migratetomanual')) {
if (!$this->get_setting_value('users') && $this->get_setting_value('enrolments') == backup::ENROL_WITHUSERS) {
$converttomanual = true;
} else {
$converttomanual = ($this->get_setting_value('enrolments') == backup::ENROL_NEVER);
}

if ($converttomanual) {
// Restore enrolments as manual enrolments.
unset($data->sortorder); // Remove useless sortorder from <2.4 backups.
if (!enrol_is_enabled('manual')) {
$this->set_mapping('enrol', $oldid, 0);
Expand All @@ -2224,7 +2236,7 @@ public function process_enrol($data) {
} else {
if (!enrol_is_enabled($data->enrol) or !isset($this->plugins[$data->enrol])) {
$this->set_mapping('enrol', $oldid, 0);
$message = "Enrol plugin '$data->enrol' data can not be restored because it is not enabled, use migration to manual enrolments";
$message = "Enrol plugin '$data->enrol' data can not be restored because it is not enabled, consider restoring without enrolment methods";
$this->log($message, backup::LOG_WARNING);
return;
}
Expand Down
261 changes: 261 additions & 0 deletions backup/moodle2/tests/moodle2_test.php
Expand Up @@ -541,4 +541,265 @@ protected function duplicate($course, $cmid) {
}
return $newcmid;
}

/**
* Help function for enrolment methods backup/restore tests:
*
* - Creates a course ($course), adds self-enrolment method and a user
* - Makes a backup
* - Creates a target course (if requested) ($newcourseid)
* - Initialises restore controller for this backup file ($rc)
*
* @param int $target target for restoring: backup::TARGET_NEW_COURSE etc.
* @param array $additionalcaps - additional capabilities to give to user
* @return array array of original course, new course id, restore controller: [$course, $newcourseid, $rc]
*/
protected function prepare_for_enrolments_test($target, $additionalcaps = []) {
global $CFG, $DB;
$this->resetAfterTest(true);

// Turn off file logging, otherwise it can't delete the file (Windows).
$CFG->backup_file_logger_level = backup::LOG_NONE;

$user = $this->getDataGenerator()->create_user();
$roleidcat = create_role('Category role', 'dummyrole1', 'dummy role description');

$course = $this->getDataGenerator()->create_course();

// Enable instance of self-enrolment plugin (it should already be present) and enrol a student with it.
$selfplugin = enrol_get_plugin('self');
$selfinstance = $DB->get_record('enrol', array('courseid' => $course->id, 'enrol' => 'self'));
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
$selfplugin->update_status($selfinstance, ENROL_INSTANCE_ENABLED);
$selfplugin->enrol_user($selfinstance, $user->id, $studentrole->id);

// Give current user capabilities to do backup and restore and assign student role.
$categorycontext = context_course::instance($course->id)->get_parent_context();

$caps = array_merge([
'moodle/course:view',
'moodle/course:create',
'moodle/backup:backupcourse',
'moodle/backup:configure',
'moodle/backup:backuptargetimport',
'moodle/restore:restorecourse',
'moodle/role:assign',
'moodle/restore:configure',
], $additionalcaps);

foreach ($caps as $cap) {
assign_capability($cap, CAP_ALLOW, $roleidcat, $categorycontext);
}

allow_assign($roleidcat, $studentrole->id);
role_assign($roleidcat, $user->id, $categorycontext);
accesslib_clear_all_caches_for_unit_testing();

$this->setUser($user);

// Do backup with default settings. MODE_IMPORT means it will just
// create the directory and not zip it.
$bc = new backup_controller(backup::TYPE_1COURSE, $course->id,
backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_SAMESITE,
$user->id);
$backupid = $bc->get_backupid();
$backupbasepath = $bc->get_plan()->get_basepath();
$bc->execute_plan();
$results = $bc->get_results();
$file = $results['backup_destination'];
$bc->destroy();

// Restore the backup immediately.

// Check if we need to unzip the file because the backup temp dir does not contains backup files.
if (!file_exists($backupbasepath . "/moodle_backup.xml")) {
$file->extract_to_pathname(get_file_packer('application/vnd.moodle.backup'), $backupbasepath);
}

if ($target == backup::TARGET_NEW_COURSE) {
$newcourseid = restore_dbops::create_new_course($course->fullname . '_2',
$course->shortname . '_2',
$course->category);
} else {
$newcourse = $this->getDataGenerator()->create_course();
$newcourseid = $newcourse->id;
}
$rc = new restore_controller($backupid, $newcourseid,
backup::INTERACTIVE_NO, backup::MODE_SAMESITE, $user->id, $target);

return [$course, $newcourseid, $rc];
}

/**
* Backup a course with enrolment methods and restore it without user data and without enrolment methods
*/
public function test_restore_without_users_without_enrolments() {
global $DB;

list($course, $newcourseid, $rc) = $this->prepare_for_enrolments_test(backup::TARGET_NEW_COURSE);

// Ensure enrolment methods will not be restored without capability.
$this->assertEquals(backup::ENROL_NEVER, $rc->get_plan()->get_setting('enrolments')->get_value());
$this->assertEquals(false, $rc->get_plan()->get_setting('users')->get_value());

$this->assertTrue($rc->execute_precheck());
$rc->execute_plan();
$rc->destroy();

// Self-enrolment method was not enabled, users were not restored.
$this->assertEmpty($DB->count_records('enrol', ['enrol' => 'self', 'courseid' => $newcourseid,
'status' => ENROL_INSTANCE_ENABLED]));
$sql = "select ue.id, ue.userid, e.enrol from {user_enrolments} ue
join {enrol} e on ue.enrolid = e.id WHERE e.courseid = ?";
$enrolments = $DB->get_records_sql($sql, [$newcourseid]);
$this->assertEmpty($enrolments);
}

/**
* Backup a course with enrolment methods and restore it without user data with enrolment methods
*/
public function test_restore_without_users_with_enrolments() {
global $DB;

list($course, $newcourseid, $rc) = $this->prepare_for_enrolments_test(backup::TARGET_NEW_COURSE,
['moodle/course:enrolconfig']);

// Ensure enrolment methods will be restored.
$this->assertEquals(backup::ENROL_NEVER, $rc->get_plan()->get_setting('enrolments')->get_value());
$this->assertEquals(false, $rc->get_plan()->get_setting('users')->get_value());
// Set "Include enrolment methods" to "Always" so they can be restored without users.
$rc->get_plan()->get_setting('enrolments')->set_value(backup::ENROL_ALWAYS);

$this->assertTrue($rc->execute_precheck());
$rc->execute_plan();
$rc->destroy();

// Self-enrolment method was restored (it is enabled), users were not restored.
$enrol = $DB->get_records('enrol', ['enrol' => 'self', 'courseid' => $newcourseid,
'status' => ENROL_INSTANCE_ENABLED]);
$this->assertNotEmpty($enrol);

$sql = "select ue.id, ue.userid, e.enrol from {user_enrolments} ue
join {enrol} e on ue.enrolid = e.id WHERE e.courseid = ?";
$enrolments = $DB->get_records_sql($sql, [$newcourseid]);
$this->assertEmpty($enrolments);
}

/**
* Backup a course with enrolment methods and restore it with user data and without enrolment methods
*/
public function test_restore_with_users_without_enrolments() {
global $DB;

list($course, $newcourseid, $rc) = $this->prepare_for_enrolments_test(backup::TARGET_NEW_COURSE,
['moodle/backup:userinfo', 'moodle/restore:userinfo']);

// Ensure enrolment methods will not be restored without capability.
$this->assertEquals(backup::ENROL_NEVER, $rc->get_plan()->get_setting('enrolments')->get_value());
$this->assertEquals(true, $rc->get_plan()->get_setting('users')->get_value());

global $qwerty;
$qwerty = 1;
$this->assertTrue($rc->execute_precheck());
$rc->execute_plan();
$rc->destroy();
$qwerty = 0;

// Self-enrolment method was not restored, student was restored as manual enrolment.
$this->assertEmpty($DB->count_records('enrol', ['enrol' => 'self', 'courseid' => $newcourseid,
'status' => ENROL_INSTANCE_ENABLED]));

$enrol = $DB->get_record('enrol', ['enrol' => 'manual', 'courseid' => $newcourseid]);
$this->assertEquals(1, $DB->count_records('user_enrolments', ['enrolid' => $enrol->id]));
}

/**
* Backup a course with enrolment methods and restore it with user data with enrolment methods
*/
public function test_restore_with_users_with_enrolments() {
global $DB;

list($course, $newcourseid, $rc) = $this->prepare_for_enrolments_test(backup::TARGET_NEW_COURSE,
['moodle/backup:userinfo', 'moodle/restore:userinfo', 'moodle/course:enrolconfig']);

// Ensure enrolment methods will be restored.
$this->assertEquals(backup::ENROL_WITHUSERS, $rc->get_plan()->get_setting('enrolments')->get_value());
$this->assertEquals(true, $rc->get_plan()->get_setting('users')->get_value());

$this->assertTrue($rc->execute_precheck());
$rc->execute_plan();
$rc->destroy();

// Self-enrolment method was restored (it is enabled), student was restored.
$enrol = $DB->get_records('enrol', ['enrol' => 'self', 'courseid' => $newcourseid,
'status' => ENROL_INSTANCE_ENABLED]);
$this->assertNotEmpty($enrol);

$sql = "select ue.id, ue.userid, e.enrol from {user_enrolments} ue
join {enrol} e on ue.enrolid = e.id WHERE e.courseid = ?";
$enrolments = $DB->get_records_sql($sql, [$newcourseid]);
$this->assertEquals(1, count($enrolments));
$enrolment = reset($enrolments);
$this->assertEquals('self', $enrolment->enrol);
}

/**
* Backup a course with enrolment methods and restore it with user data with enrolment methods merging into another course
*/
public function test_restore_with_users_with_enrolments_merging() {
global $DB;

list($course, $newcourseid, $rc) = $this->prepare_for_enrolments_test(backup::TARGET_EXISTING_ADDING,
['moodle/backup:userinfo', 'moodle/restore:userinfo', 'moodle/course:enrolconfig']);

// Ensure enrolment methods will be restored.
$this->assertEquals(backup::ENROL_WITHUSERS, $rc->get_plan()->get_setting('enrolments')->get_value());
$this->assertEquals(true, $rc->get_plan()->get_setting('users')->get_value());

$this->assertTrue($rc->execute_precheck());
$rc->execute_plan();
$rc->destroy();

// User was restored with self-enrolment method.
$enrol = $DB->get_records('enrol', ['enrol' => 'self', 'courseid' => $newcourseid,
'status' => ENROL_INSTANCE_ENABLED]);
$this->assertNotEmpty($enrol);

$sql = "select ue.id, ue.userid, e.enrol from {user_enrolments} ue
join {enrol} e on ue.enrolid = e.id WHERE e.courseid = ?";
$enrolments = $DB->get_records_sql($sql, [$newcourseid]);
$this->assertEquals(1, count($enrolments));
$enrolment = reset($enrolments);
$this->assertEquals('self', $enrolment->enrol);
}

/**
* Backup a course with enrolment methods and restore it with user data with enrolment methods into another course deleting it's contents
*/
public function test_restore_with_users_with_enrolments_deleting() {
global $DB;

list($course, $newcourseid, $rc) = $this->prepare_for_enrolments_test(backup::TARGET_EXISTING_DELETING,
['moodle/backup:userinfo', 'moodle/restore:userinfo', 'moodle/course:enrolconfig']);

// Ensure enrolment methods will be restored.
$this->assertEquals(backup::ENROL_WITHUSERS, $rc->get_plan()->get_setting('enrolments')->get_value());
$this->assertEquals(true, $rc->get_plan()->get_setting('users')->get_value());

$this->assertTrue($rc->execute_precheck());
$rc->execute_plan();
$rc->destroy();

// Self-enrolment method was restored (it is enabled), student was restored.
$enrol = $DB->get_records('enrol', ['enrol' => 'self', 'courseid' => $newcourseid,
'status' => ENROL_INSTANCE_ENABLED]);
$this->assertNotEmpty($enrol);

$sql = "select ue.id, ue.userid, e.enrol from {user_enrolments} ue
join {enrol} e on ue.enrolid = e.id WHERE e.courseid = ?";
$enrolments = $DB->get_records_sql($sql, [$newcourseid]);
$this->assertEquals(1, count($enrolments));
$enrolment = reset($enrolments);
$this->assertEquals('self', $enrolment->enrol);
}
}
14 changes: 14 additions & 0 deletions backup/util/checks/restore_check.class.php
Expand Up @@ -203,6 +203,20 @@ public static function check_security($restore_controller, $apply) {
$overwritesetting = $restore_controller->get_plan()->get_setting('overwrite_conf');
$overwritesetting->set_status(base_setting::LOCKED_BY_PERMISSION);
}

// Ensure the user has the capability to manage enrolment methods. If not we want to unset and lock
// the setting so that they cannot change it.
$hasmanageenrolcap = has_capability('moodle/course:enrolconfig', $coursectx, $userid);
if (!$hasmanageenrolcap) {
if ($restore_controller->get_plan()->setting_exists('enrolments')) {
$enrolsetting = $restore_controller->get_plan()->get_setting('enrolments');
if ($enrolsetting->get_value() != backup::ENROL_NEVER) {
$enrolsetting->set_status(base_setting::NOT_LOCKED); // In case it was locked earlier.
$enrolsetting->set_value(backup::ENROL_NEVER);
}
$enrolsetting->set_status(base_setting::LOCKED_BY_PERMISSION);
}
}
}

return true;
Expand Down

0 comments on commit 92253b1

Please sign in to comment.