protected function define_execution() { global $CFG; restore_controller_dbops::drop_restore_temp_tables($this->get_restoreid()); // Drop ids temp table backup_helper::delete_old_backup_dirs(time() - (4 * 60 * 60)); // Delete > 4 hours temp dirs if (empty($CFG->keeptempdirectoriesonbackup)) { // Conditionally backup_helper::delete_backup_dir($this->task->get_tempdir()); // Empty restore dir } }
protected function define_execution() { global $CFG; restore_controller_dbops::drop_restore_temp_tables($this->get_restoreid()); // Drop ids temp table $progress = $this->task->get_progress(); $progress->start_progress('Deleting backup dir'); backup_helper::delete_old_backup_dirs(strtotime('-1 week'), $progress); // Delete > 1 week old temp dirs. if (empty($CFG->keeptempdirectoriesonbackup)) { // Conditionally backup_helper::delete_backup_dir($this->task->get_tempdir(), $progress); // Empty restore dir } $progress->end_progress(); }
/** * Return one array containing all the tasks that have been included * in the restore process. Note that these tasks aren't built (they * haven't steps nor ids data available) */ public static function get_included_tasks($restoreid) { $rc = restore_controller_dbops::load_controller($restoreid); $tasks = $rc->get_plan()->get_tasks(); $includedtasks = array(); foreach ($tasks as $key => $task) { // Calculate if the task is being included $included = false; // blocks, based in blocks setting and parent activity/course if ($task instanceof restore_block_task) { if (!$task->get_setting_value('blocks')) { // Blocks not included, continue continue; } $parent = basename(dirname(dirname($task->get_taskbasepath()))); if ($parent == 'course') { // Parent is course, always included if present $included = true; } else { // Look for activity_included setting $included = $task->get_setting_value($parent . '_included'); } // ativities, based on included setting } else { if ($task instanceof restore_activity_task) { $included = $task->get_setting_value('included'); // sections, based on included setting } else { if ($task instanceof restore_section_task) { $included = $task->get_setting_value('included'); // course always included if present } else { if ($task instanceof restore_course_task) { $included = true; } } } } // If included, add it if ($included) { $includedtasks[] = $task; } } return $includedtasks; }
/** * Entry point for all the prechecks to be performed before restore * * Returns empty array or warnings/errors array */ public static function execute_prechecks(restore_controller $controller, $droptemptablesafter = false) { global $CFG; $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(); $rolemappings = $controller->get_info()->role_mappings; $progress = $controller->get_progress(); // Start tracking progress. There are currently 8 major steps, corresponding // to $majorstep++ lines in this code; we keep track of the total so as to // verify that it's still correct. If you add a major step, you need to change // the total here. $majorstep = 1; $majorsteps = 8; $progress->start_progress('Carrying out pre-restore checks', $majorsteps); // Load all the included tasks to look for inforef.xml files $inforeffiles = array(); $tasks = restore_dbops::get_included_tasks($restoreid); $progress->start_progress('Listing inforef files', count($tasks)); $minorstep = 1; foreach ($tasks as $task) { // Add the inforef.xml file if exists $inforefpath = $task->get_taskbasepath() . '/inforef.xml'; if (file_exists($inforefpath)) { $inforeffiles[] = $inforefpath; } $progress->progress($minorstep++); } $progress->end_progress(); $progress->progress($majorstep++); // Create temp tables restore_controller_dbops::create_restore_temp_tables($controller->get_restoreid()); // Check we are restoring one backup >= $min20version (very first ok ever) $min20version = 2010072300; if ($controller->get_info()->backup_version < $min20version) { $message = new stdclass(); $message->backup = $controller->get_info()->backup_version; $message->min = $min20version; $errors[] = get_string('errorminbackup20version', 'backup', $message); } // Compare Moodle's versions if ($CFG->version < $controller->get_info()->moodle_version) { $message = new stdclass(); $message->serverversion = $CFG->version; $message->serverrelease = $CFG->release; $message->backupversion = $controller->get_info()->moodle_version; $message->backuprelease = $controller->get_info()->moodle_release; $warnings[] = get_string('noticenewerbackup', '', $message); } // Error if restoring over frontpage // TODO: Review the whole restore process in order to transform this into one warning (see 1.9) if ($controller->get_courseid() == SITEID) { $errors[] = get_string('errorrestorefrontpage', 'backup'); } // If restoring to different site and restoring users and backup has mnet users warn/error if (!$samesite && $restoreusers && $hasmnetusers) { // User is admin (can create users at sysctx), warn if (has_capability('moodle/user:create', context_system::instance(), $controller->get_userid())) { $warnings[] = get_string('mnetrestore_extusers_admin', 'admin'); // User not admin } else { $errors[] = get_string('mnetrestore_extusers_noadmin', 'admin'); } } // Load all the inforef files, we are going to need them $progress->start_progress('Loading temporary IDs', count($inforeffiles)); $minorstep = 1; foreach ($inforeffiles as $inforeffile) { // Load each inforef file to temp_ids. restore_dbops::load_inforef_to_tempids($restoreid, $inforeffile, $progress); $progress->progress($minorstep++); } $progress->end_progress(); $progress->progress($majorstep++); // If restoring users, check we are able to create all them if ($restoreusers) { $file = $controller->get_plan()->get_basepath() . '/users.xml'; // Load needed users to temp_ids. restore_dbops::load_users_to_tempids($restoreid, $file, $progress); $progress->progress($majorstep++); if ($problems = restore_dbops::precheck_included_users($restoreid, $courseid, $userid, $samesite, $progress)) { $errors = array_merge($errors, $problems); } } else { // To ensure consistent number of steps in progress tracking, // mark progress even though we didn't do anything. $progress->progress($majorstep++); } $progress->progress($majorstep++); // 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 roles to temp_ids if ($problems = restore_dbops::precheck_included_roles($restoreid, $courseid, $userid, $samesite, $rolemappings)) { $errors = array_key_exists('errors', $problems) ? array_merge($errors, $problems['errors']) : $errors; $warnings = array_key_exists('warnings', $problems) ? array_merge($warnings, $problems['warnings']) : $warnings; } $progress->progress($majorstep++); // Check we are able to restore and the categories and questions $file = $controller->get_plan()->get_basepath() . '/questions.xml'; restore_dbops::load_categories_and_questions_to_tempids($restoreid, $file); if ($problems = restore_dbops::precheck_categories_and_questions($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; } $progress->progress($majorstep++); // Prepare results. $results = array(); if (!empty($errors)) { $results['errors'] = $errors; } if (!empty($warnings)) { $results['warnings'] = $warnings; } // Warnings/errors detected or want to do so explicitly, drop temp tables if (!empty($results) || $droptemptablesafter) { restore_controller_dbops::drop_restore_temp_tables($controller->get_restoreid()); } // Finish progress and check we got the initial number of steps right. $progress->progress($majorstep++); if ($majorstep != $majorsteps) { throw new coding_exception('Progress step count wrong: ' . $majorstep); } $progress->end_progress(); return $results; }
public static function load_controller($restoreid) { // Load controller from persistent storage // TODO: flag the controller as available. Operations on it can continue $controller = restore_controller_dbops::load_controller($restoreid); $controller->log('loading controller from db', backup::LOG_DEBUG); return $controller; }
/** * Verify the xxx_ids_cached (in-memory backup_ids cache) stuff works as expected. * * Note that those private implementations are tested here by using the public * backup_ids API and later performing low-level tests. */ public function test_backup_ids_cached() { global $DB; $dbman = $DB->get_manager(); // We are going to use database_manager services. $this->resetAfterTest(true); // Playing with temp tables, better reset once finished. // Some variables and objects for testing. $restoreid = 'testrestoreid'; $mapping = new stdClass(); $mapping->itemname = 'user'; $mapping->itemid = 1; $mapping->newitemid = 2; $mapping->parentitemid = 3; $mapping->info = 'info'; // Create the backup_ids temp tables used by restore. restore_controller_dbops::create_restore_temp_tables($restoreid); // Send one mapping using the public api with defaults. restore_dbops::set_backup_ids_record($restoreid, $mapping->itemname, $mapping->itemid); // Get that mapping and verify everything is returned as expected. $result = restore_dbops::get_backup_ids_record($restoreid, $mapping->itemname, $mapping->itemid); $this->assertSame($mapping->itemname, $result->itemname); $this->assertSame($mapping->itemid, $result->itemid); $this->assertSame(0, $result->newitemid); $this->assertSame(null, $result->parentitemid); $this->assertSame(null, $result->info); // Drop the backup_xxx_temp temptables manually, so memory cache won't be invalidated. $dbman->drop_table(new xmldb_table('backup_ids_temp')); $dbman->drop_table(new xmldb_table('backup_files_temp')); // Verify the mapping continues returning the same info, // now from cache (the table does not exist). $result = restore_dbops::get_backup_ids_record($restoreid, $mapping->itemname, $mapping->itemid); $this->assertSame($mapping->itemname, $result->itemname); $this->assertSame($mapping->itemid, $result->itemid); $this->assertSame(0, $result->newitemid); $this->assertSame(null, $result->parentitemid); $this->assertSame(null, $result->info); // Recreate the temp table, just to drop it using the restore API in // order to check that, then, the cache becomes invalid for the same request. restore_controller_dbops::create_restore_temp_tables($restoreid); restore_controller_dbops::drop_restore_temp_tables($restoreid); // No cached info anymore, so the mapping request will arrive to // DB leading to error (temp table does not exist). try { $result = restore_dbops::get_backup_ids_record($restoreid, $mapping->itemname, $mapping->itemid); $this->fail('Expecting an exception, none occurred'); } catch (Exception $e) { $this->assertTrue($e instanceof dml_exception); $this->assertSame('Table "backup_ids_temp" does not exist', $e->getMessage()); } // Create the backup_ids temp tables once more. restore_controller_dbops::create_restore_temp_tables($restoreid); // Send one mapping using the public api with complete values. restore_dbops::set_backup_ids_record($restoreid, $mapping->itemname, $mapping->itemid, $mapping->newitemid, $mapping->parentitemid, $mapping->info); // Get that mapping and verify everything is returned as expected. $result = restore_dbops::get_backup_ids_record($restoreid, $mapping->itemname, $mapping->itemid); $this->assertSame($mapping->itemname, $result->itemname); $this->assertSame($mapping->itemid, $result->itemid); $this->assertSame($mapping->newitemid, $result->newitemid); $this->assertSame($mapping->parentitemid, $result->parentitemid); $this->assertSame($mapping->info, $result->info); // Finally, drop the temp tables properly and get the DB error again (memory caches empty). restore_controller_dbops::drop_restore_temp_tables($restoreid); try { $result = restore_dbops::get_backup_ids_record($restoreid, $mapping->itemname, $mapping->itemid); $this->fail('Expecting an exception, none occurred'); } catch (Exception $e) { $this->assertTrue($e instanceof dml_exception); $this->assertSame('Table "backup_ids_temp" does not exist', $e->getMessage()); } }
/** * Check all the included users, deciding the action to perform * for each one (mapping / creation) and returning one array * of problems in case something is wrong (lack of permissions, * conficts) * * @param string $restoreid Restore id * @param int $courseid Course id * @param int $userid User id * @param bool $samesite True if restore is to same site * @param \core\progress\base $progress Progress reporter */ public static function precheck_included_users($restoreid, $courseid, $userid, $samesite, \core\progress\base $progress) { global $CFG, $DB; // To return any problem found $problems = array(); // We are going to map mnethostid, so load all the available ones $mnethosts = $DB->get_records('mnet_host', array(), 'wwwroot', 'wwwroot, id'); // Calculate the context we are going to use for capability checking $context = context_course::instance($courseid); // When conflicting users are detected we may need original site info. $restoreinfo = restore_controller_dbops::load_controller($restoreid)->get_info(); // Calculate if we have perms to create users, by checking: // to 'moodle/restore:createuser' and 'moodle/restore:userinfo' // and also observe $CFG->disableusercreationonrestore $cancreateuser = false; if (has_capability('moodle/restore:createuser', $context, $userid) and has_capability('moodle/restore:userinfo', $context, $userid) and empty($CFG->disableusercreationonrestore)) { // Can create users $cancreateuser = true; } // Prepare for reporting progress. $conditions = array('backupid' => $restoreid, 'itemname' => 'user'); $max = $DB->count_records('backup_ids_temp', $conditions); $done = 0; $progress->start_progress('Checking users', $max); // Iterate over all the included users $rs = $DB->get_recordset('backup_ids_temp', $conditions, '', 'itemid, info'); foreach ($rs as $recuser) { $user = (object) backup_controller_dbops::decode_backup_temp_info($recuser->info); // Find the correct mnethostid for user before performing any further check if (empty($user->mnethosturl) || $user->mnethosturl === $CFG->wwwroot) { $user->mnethostid = $CFG->mnet_localhost_id; } else { // fast url-to-id lookups if (isset($mnethosts[$user->mnethosturl])) { $user->mnethostid = $mnethosts[$user->mnethosturl]->id; } else { $user->mnethostid = $CFG->mnet_localhost_id; } } // Now, precheck that user and, based on returned results, annotate action/problem $usercheck = self::precheck_user($user, $samesite, $restoreinfo->original_site_identifier_hash); if (is_object($usercheck)) { // No problem, we have found one user in DB to be mapped to // Annotate it, for later process. Set newitemid to mapping user->id self::set_backup_ids_record($restoreid, 'user', $recuser->itemid, $usercheck->id); } else { if ($usercheck === false) { // Found conflict, report it as problem if (!get_config('backup', 'import_general_duplicate_admin_allowed')) { $problems[] = get_string('restoreuserconflict', '', $user->username); } else { if ($user->username == 'admin') { if (!$cancreateuser) { $problems[] = get_string('restorecannotcreateuser', '', $user->username); } if ($user->mnethostid != $CFG->mnet_localhost_id) { $problems[] = get_string('restoremnethostidmismatch', '', $user->username); } if (!$problems) { // Duplicate admin allowed, append original site idenfitier to username. $user->username .= '_' . $restoreinfo->original_site_identifier_hash; self::set_backup_ids_record($restoreid, 'user', $recuser->itemid, 0, null, (array) $user); } } } } else { if ($usercheck === true) { // User needs to be created, check if we are able if ($cancreateuser) { // Can create user, set newitemid to 0 so will be created later self::set_backup_ids_record($restoreid, 'user', $recuser->itemid, 0, null, (array) $user); } else { // Cannot create user, report it as problem $problems[] = get_string('restorecannotcreateuser', '', $user->username); } } else { // Shouldn't arrive here ever, something is for sure wrong. Exception throw new restore_dbops_exception('restore_error_processing_user', $user->username); } } } $done++; $progress->progress($done); } $rs->close(); $progress->end_progress(); return $problems; }
/** * Entry point for all the prechecks to be performed before restore * * Returns empty array or warnings/errors array */ public static function execute_prechecks($controller, $droptemptablesafter = false) { global $CFG; $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(); $rolemappings = $controller->get_info()->role_mappings; // Load all the included tasks to look for inforef.xml files $inforeffiles = array(); $tasks = restore_dbops::get_included_tasks($restoreid); foreach ($tasks as $task) { // Add the inforef.xml file if exists $inforefpath = $task->get_taskbasepath() . '/inforef.xml'; if (file_exists($inforefpath)) { $inforeffiles[] = $inforefpath; } } // Create temp tables restore_controller_dbops::create_restore_temp_tables($controller->get_restoreid()); // Check we are restoring one backup >= $min20version (very first ok ever) $min20version = 2010072300; if ($controller->get_info()->backup_version < $min20version) { $message = new stdclass(); $message->backup = $controller->get_info()->backup_version; $message->min = $min20version; $errors[] = get_string('errorminbackup20version', 'backup', $message); } // Compare Moodle's versions if ($CFG->version < $controller->get_info()->moodle_version) { $message = new stdclass(); $message->serverversion = $CFG->version; $message->serverrelease = $CFG->release; $message->backupversion = $controller->get_info()->moodle_version; $message->backuprelease = $controller->get_info()->moodle_release; $warnings[] = get_string('noticenewerbackup', '', $message); } // Error if restoring over frontpage // TODO: Review the whole restore process in order to transform this into one warning (see 1.9) if ($controller->get_courseid() == SITEID) { $errors[] = get_string('errorrestorefrontpage', 'backup'); } // If restoring to different site and restoring users and backup has mnet users warn/error if (!$samesite && $restoreusers && $hasmnetusers) { // User is admin (can create users at sysctx), warn if (has_capability('moodle/user:create', context_system::instance(), $controller->get_userid())) { $warnings[] = get_string('mnetrestore_extusers_admin', 'admin'); // User not admin } else { $errors[] = get_string('mnetrestore_extusers_noadmin', 'admin'); } } // 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'; 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); } } // 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 roles to temp_ids if ($problems = restore_dbops::precheck_included_roles($restoreid, $courseid, $userid, $samesite, $rolemappings)) { $errors = array_key_exists('errors', $problems) ? array_merge($errors, $problems['errors']) : $errors; $warnings = array_key_exists('warnings', $problems) ? array_merge($warnings, $problems['warnings']) : $warnings; } // Check we are able to restore and the categories and questions $file = $controller->get_plan()->get_basepath() . '/questions.xml'; restore_dbops::load_categories_and_questions_to_tempids($restoreid, $file); if ($problems = restore_dbops::precheck_categories_and_questions($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(); if (!empty($errors)) { $results['errors'] = $errors; } if (!empty($warnings)) { $results['warnings'] = $warnings; } // Warnings/errors detected or want to do so explicitly, drop temp tables if (!empty($results) || $droptemptablesafter) { restore_controller_dbops::drop_restore_temp_tables($controller->get_restoreid()); } return $results; }