diff --git a/backup/moodle2/restore_root_task.class.php b/backup/moodle2/restore_root_task.class.php index 004809a04ae..c2d0f7d60ff 100644 --- a/backup/moodle2/restore_root_task.class.php +++ b/backup/moodle2/restore_root_task.class.php @@ -40,6 +40,9 @@ class restore_root_task extends restore_task { // If we haven't preloaded information, load all the included inforef records to temp_ids table $this->add_step(new restore_load_included_inforef_records('load_inforef_records')); + // If we haven't preloaded information, load all the needed roles to temp_ids_table + $this->add_step(new restore_load_and_map_roles('load_and_map_roles')); + // If we haven't preloaded information and are restoring user info, load all the needed users to temp_ids table $this->add_step(new restore_load_included_users('load_user_records')); diff --git a/backup/moodle2/restore_stepslib.php b/backup/moodle2/restore_stepslib.php index 98dc45b4f3f..04d761bcbb2 100644 --- a/backup/moodle2/restore_stepslib.php +++ b/backup/moodle2/restore_stepslib.php @@ -108,15 +108,41 @@ class restore_load_included_files extends restore_structure_step { // load it if needed: // - it it is one of the annotated inforef files (course/section/activity/block) - // - it is one "user", "group" or "grade" component file + // - it is one "user", "group", "grouping" or "grade" component file (that aren't sent to inforef ever) $isfileref = restore_dbops::get_backup_ids_record($this->get_restoreid(), 'fileref', $data->id); - $iscomponent = ($data->component == 'user' || $data->component == 'group' || $data->component == 'grade'); + $iscomponent = ($data->component == 'user' || $data->component == 'group' || + $data->component == 'grouping' || $data->component == 'grade'); if ($isfileref || $iscomponent) { restore_dbops::set_backup_files_record($this->get_restoreid(), $data); } } } +/** + * Execution step that, *conditionally* (if there isn't preloaded information), + * will load all the needed roles to backup_temp_ids. They will be stored with + * "role" itemname. Also it will perform one automatic mapping to roles existing + * in the target site, based in permissions of the user performing the restore, + * archetypes and other bits. At the end, each original role will have its associated + * target role or 0 if it's going to be skipped. Note we wrap everything over one + * restore_dbops method, as far as the same stuff is going to be also executed + * by restore prechecks + */ +class restore_load_and_map_roles extends restore_execution_step { + + protected function define_execution() { + if ($this->task->get_preloaded_information()) { // if info is already preloaded, nothing to do + return; + } + + $file = $this->get_basepath() . '/roles.xml'; + // Load needed toles to temp_ids + restore_dbops::load_roles_to_tempids($this->get_restoreid(), $file); + // Process roles, mapping/skipping. Any error throws exception + restore_dbops::process_included_roles($this->get_restoreid(), $this->task->get_courseid(), $this->task->get_userid(), $this->task->is_samesite()); + } +} + /** * Execution step that, *conditionally* (if there isn't preloaded information * and users have been selected in settings, will load all the needed users @@ -243,17 +269,53 @@ class restore_groups_structure_step extends restore_structure_step { } public function process_grouping($data) { - debugging('TODO: Grouping restore not implemented. Detected grouping', DEBUG_DEVELOPER); + global $DB; + + $data = (object)$data; // handy + $data->courseid = $this->get_courseid(); + + $oldid = $data->id; // need this saved for later + $restorefiles = false; // Only if we end creating the grouping + + // Search if the grouping already exists (by name & description) in the target course + $description_clause = ''; + $params = array('courseid' => $this->get_courseid(), 'grname' => $data->name); + if (!empty($data->description)) { + $description_clause = ' AND ' . + $DB->sql_compare_text('description') . ' = ' . $DB->sql_compare_text(':desc'); + $params['desc'] = $data->description; + } + if (!$groupingdb = $DB->get_record_sql("SELECT * + FROM {groupings} + WHERE courseid = :courseid + AND name = :grname $description_clause", $params)) { + // grouping doesn't exist, create + $newitemid = $DB->insert_record('groupings', $data); + $restorefiles = true; // We'll restore the files + } else { + // grouping exists, use it + $newitemid = $groupingdb->id; + } + // Save the id mapping + $this->set_mapping('grouping', $oldid, $newitemid, $restorefiles); } public function process_grouping_group($data) { - debugging('TODO: Grouping restore not implemented. Detected grouping group', DEBUG_DEVELOPER); + global $DB; + + $data = (object)$data; + + $data->groupingid = $this->get_new_parentid('grouping'); // Use new parentid + $data->groupid = $this->get_mappingid('group', $data->groupid); // Get from mappings + $DB->insert_record('groupings_groups', $data); // No need to set this mapping (no child info nor files) } protected function after_execute() { // Add group related files, matching with "group" mappings $this->add_related_files('group', 'icon', 'group'); $this->add_related_files('group', 'description', 'group'); + // Add grouping related files, matching with "grouping" mappings + $this->add_related_files('grouping', 'description', 'grouping'); } } @@ -339,6 +401,7 @@ class restore_course_structure_step extends restore_structure_step { // Processing functions go here public function process_course($data) { + // TODO: don't forget to remap defaultgroupingid print_object('stopped before processing course. Continue here'); } diff --git a/backup/util/dbops/restore_dbops.class.php b/backup/util/dbops/restore_dbops.class.php index 3a68ce24179..80f478b918c 100644 --- a/backup/util/dbops/restore_dbops.class.php +++ b/backup/util/dbops/restore_dbops.class.php @@ -94,6 +94,50 @@ abstract class restore_dbops { $xmlparser->process(); } + /** + * Load the needed role.xml file to backup_ids table for future reference + */ + public static function load_roles_to_tempids($restoreid, $rolesfile) { + + if (!file_exists($rolesfile)) { // Shouldn't happen ever, but... + throw new backup_helper_exception('missing_roles_xml_file', $rolesfile); + } + // Let's parse, custom processor will do its work, sending info to DB + $xmlparser = new progressive_parser(); + $xmlparser->set_file($rolesfile); + $xmlprocessor = new restore_roles_parser_processor($restoreid); + $xmlparser->set_processor($xmlprocessor); + $xmlparser->process(); + } + + /** + * Precheck the loaded roles, return empty array if everything is ok, and + * array with 'errors', 'warnings' elements (suitable to be used by restore_prechecks) + * with any problem found + */ + public static function precheck_included_roles($restoreid, $courseid, $userid, $samesite) { + debugging('implement the roles mapping/skip here. returns errors/warnings array', DEBUG_DEVELOPER); + return array(); + } + + /** + * Process the loaded roles, looking for their best mapping or skipping + * Any error will cause exception. Note this is one wrapper over + * precheck_included_roles, that contains all the logic, but returns + * errors/warnings instead and is executed as part of the restore prechecks + */ + public static function process_included_roles($restoreid, $courseid, $userid, $samesite) { + global $DB; + + // Just let precheck_included_roles() to do all the hard work + $problems = self::precheck_included_roles($restoreid, $courseid, $userid, $samesite); + + // With problems of type error, throw exception, shouldn't happen if prechecks executed + if (array_key_exists('errors', $problems)) { + throw new restore_dbops_exception('restore_problems_processing_roles', null, implode(', ', $problems['errors'])); + } + } + /** * Load the needed users.xml file to backup_ids table for future reference */ @@ -152,9 +196,9 @@ abstract class restore_dbops { $params = array($restoreid, $oldcontextid, $component, $filearea, $itemname); } - $rs = $DB->get_recordset_sql($sql, $params); $fs = get_file_storage(); // Get moodle file storage $basepath = $basepath . '/files/';// Get backup file pool base + $rs = $DB->get_recordset_sql($sql, $params); foreach ($rs as $rec) { $file = (object)unserialize(base64_decode($rec->info)); // ignore root dirs (they are created automatically) diff --git a/backup/util/helper/restore_prechecks_helper.class.php b/backup/util/helper/restore_prechecks_helper.class.php index 87d5c7e61f2..bf90cc1628b 100644 --- a/backup/util/helper/restore_prechecks_helper.class.php +++ b/backup/util/helper/restore_prechecks_helper.class.php @@ -49,6 +49,15 @@ abstract class restore_prechecks_helper { $errors = array(); $warnings = array(); + // Some handy vars to be used along the prechecks + $samesite = $controller->is_samesite(); + $restoreusers = $controller->get_plan()->get_setting('users')->get_value(); + $hasmnetusers = (int)$controller->get_info()->mnet_remoteusers; + $restoreid = $controller->get_restoreid(); + $courseid = $controller->get_courseid(); + $userid = $controller->get_userid(); + $inforeffiles = restore_dbops::get_needed_inforef_files($restoreid); // Get all inforef files + // Create temp tables restore_controller_dbops::create_restore_temp_tables($controller->get_restoreid()); @@ -78,9 +87,6 @@ abstract class restore_prechecks_helper { } // If restoring to different site and restoring users and backup has mnet users warn/error - $samesite = $controller->is_samesite(); - $restoreusers = $controller->get_plan()->get_setting('users')->get_value(); - $hasmnetusers = (int)$controller->get_info()->mnet_remoteusers; if (!$samesite && $restoreusers && $hasmnetusers) { // User is admin (can create users at sysctx), warn if (has_capability('moodle/user:create', get_context_instance(CONTEXT_SYSTEM), $controller->get_userid())) { @@ -91,16 +97,14 @@ abstract class restore_prechecks_helper { } } + // Load all the inforef files, we are going to need them + foreach ($inforeffiles as $inforeffile) { + restore_dbops::load_inforef_to_tempids($restoreid, $inforeffile); // Load each inforef file to temp_ids + } + // If restoring users, check we are able to create all them if ($restoreusers) { $file = $controller->get_plan()->get_basepath() . '/users.xml'; - $restoreid = $controller->get_restoreid(); - $courseid = $controller->get_courseid(); - $userid = $controller->get_userid(); - $inforeffiles = restore_dbops::get_needed_inforef_files($restoreid); // Get all inforef files - foreach ($inforeffiles as $inforeffile) { - restore_dbops::load_inforef_to_tempids($restoreid, $inforeffile); // Load each inforef file to temp_ids - } restore_dbops::load_users_to_tempids($restoreid, $file); // Load needed users to temp_ids if ($problems = restore_dbops::precheck_included_users($restoreid, $courseid, $userid, $samesite)) { $errors = array_merge($errors, $problems); @@ -108,7 +112,13 @@ abstract class restore_prechecks_helper { } // TODO: Perform role_mappings, warning about non-mappable ones being ignored (see 1.9) - // (restore won't create roles in any case) + // Note: restore won't create roles at all. Only mapping/skip! + $file = $controller->get_plan()->get_basepath() . '/roles.xml'; + restore_dbops::load_roles_to_tempids($restoreid, $file); // Load needed users to temp_ids + if ($problems = restore_dbops::precheck_included_roles($restoreid, $courseid, $userid, $samesite)) { + $errors = array_key_exists('errors', $problems) ? array_merge($errors, $problems['errors']) : $errors; + $warnings = array_key_exists('warnings', $problems) ? array_merge($warnings, $problems['warnings']) : $warnings; + } // Prepare results and return $results = array(); diff --git a/backup/util/includes/restore_includes.php b/backup/util/includes/restore_includes.php index ad9dca72c6b..d211bf98f06 100644 --- a/backup/util/includes/restore_includes.php +++ b/backup/util/includes/restore_includes.php @@ -38,6 +38,7 @@ require_once($CFG->dirroot . '/backup/util/helper/restore_prechecks_helper.class require_once($CFG->dirroot . '/backup/util/helper/restore_moodlexml_parser_processor.class.php'); require_once($CFG->dirroot . '/backup/util/helper/restore_inforef_parser_processor.class.php'); require_once($CFG->dirroot . '/backup/util/helper/restore_users_parser_processor.class.php'); +require_once($CFG->dirroot . '/backup/util/helper/restore_roles_parser_processor.class.php'); require_once($CFG->dirroot . '/backup/util/helper/restore_structure_parser_processor.class.php'); require_once($CFG->dirroot . '/backup/util/xml/parser/progressive_parser.class.php'); require_once($CFG->dirroot . '/backup/util/output/output_controller.class.php');