MDL-40838 backup: restore enrolment methods without users

This commit is contained in:
Marina Glancy 2017-01-10 15:59:44 +08:00
parent 9ec952f237
commit 92253b15b7
10 changed files with 332 additions and 13 deletions

View File

@ -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

View File

@ -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

View File

@ -78,7 +78,10 @@ class restore_course_task extends restore_task {
// 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.

View File

@ -112,12 +112,26 @@ class restore_root_task extends restore_task {
$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

View File

@ -2154,12 +2154,17 @@ class restore_enrolments_structure_step extends restore_structure_step {
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;
}
/**
@ -2203,7 +2208,14 @@ class restore_enrolments_structure_step extends restore_structure_step {
$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);
@ -2224,7 +2236,7 @@ class restore_enrolments_structure_step extends restore_structure_step {
} 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;
}

View File

@ -541,4 +541,265 @@ class core_backup_moodle2_testcase extends advanced_testcase {
}
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);
}
}

View File

@ -203,6 +203,20 @@ abstract class restore_check {
$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;

View File

@ -1055,6 +1055,7 @@ class core_course_external extends external_api {
"blocks" (int) Include course blocks (default to 1 that is equal to yes),
"filters" (int) Include course filters (default to 1 that is equal to yes),
"users" (int) Include users (default to 0 that is equal to no),
"enrolments" (int) Include enrolment methods (default to 1 - restore only with users),
"role_assignments" (int) Include role assignments (default to 0 that is equal to no),
"comments" (int) Include user comments (default to 0 that is equal to no),
"userscompletion" (int) Include user course completion information (default to 0 that is equal to no),
@ -1119,6 +1120,7 @@ class core_course_external extends external_api {
'blocks' => 1,
'filters' => 1,
'users' => 0,
'enrolments' => backup::ENROL_WITHUSERS,
'role_assignments' => 0,
'comments' => 0,
'userscompletion' => 0,
@ -1174,7 +1176,9 @@ class core_course_external extends external_api {
backup::INTERACTIVE_NO, backup::MODE_SAMESITE, $USER->id);
foreach ($backupsettings as $name => $value) {
$bc->get_plan()->get_setting($name)->set_value($value);
if ($setting = $bc->get_plan()->get_setting($name)) {
$bc->get_plan()->get_setting($name)->set_value($value);
}
}
$backupid = $bc->get_backupid();

View File

@ -561,6 +561,7 @@ class enrol_self_plugin extends enrol_plugin {
$merge = array(
'courseid' => $data->courseid,
'enrol' => $this->get_name(),
'status' => $data->status,
'roleid' => $data->roleid,
);
}

View File

@ -236,6 +236,10 @@ $string['restoringcourse'] = 'Course restoration in progress';
$string['restoringcourseshortname'] = 'restoring';
$string['restorerolemappings'] = 'Restore role mappings';
$string['rootenrolmanual'] = 'Restore as manual enrolments';
$string['rootsettingenrolments'] = 'Include enrolment methods';
$string['rootsettingenrolments_always'] = 'Yes always';
$string['rootsettingenrolments_never'] = 'No, restore users as manual enrolments';
$string['rootsettingenrolments_withusers'] = 'Yes but only if users are included';
$string['rootsettings'] = 'Backup settings';
$string['rootsettingusers'] = 'Include enrolled users';
$string['rootsettinganonymize'] = 'Anonymize user information';