/** * Sets up a restore plan and stores it within the current restore controller */ protected function load_plan() { // First of all, we need to introspect the moodle_backup.xml file // in order to detect all the required stuff. So, create the // monster $info structure where everything will be defined $this->log('loading backup info', backup::LOG_DEBUG); $this->info = backup_general_helper::get_backup_information($this->tempdir); // Set the controller type to the one found in the information $this->type = $this->info->type; // Set the controller samesite flag as needed $this->samesite = backup_general_helper::backup_is_samesite($this->info); // Now we load the plan that will be configured following the // information provided by the $info $this->log('loading controller plan', backup::LOG_DEBUG); $this->plan = new rollover_restore_plan($this); $this->plan->build(); // Build plan for this controller $this->set_status(backup::STATUS_PLANNED); }
/** * Renders the destination stage screen * * @param core_backup_renderer $renderer renderer instance to use * @return string HTML code */ public function display(core_backup_renderer $renderer) { $format = backup_general_helper::detect_backup_format($this->filepath); if ($format === backup::FORMAT_MOODLE) { // Standard Moodle 2 format, let use get the type of the backup. $details = backup_general_helper::get_backup_information($this->filepath); if ($details->type === backup::TYPE_1COURSE) { $wholecourse = true; } else { $wholecourse = false; } } else { // Non-standard format to be converted. We assume it contains the // whole course for now. However, in the future there might be a callback // to the installed converters. $wholecourse = true; } $nextstageurl = new moodle_url('/backup/restore.php', array('contextid' => $this->contextid, 'filepath' => $this->filepath, 'stage' => restore_ui::STAGE_SETTINGS)); $context = context::instance_by_id($this->contextid); if ($context->contextlevel == CONTEXT_COURSE and has_capability('moodle/restore:restorecourse', $context)) { $currentcourse = $context->instanceid; } else { $currentcourse = false; } return $renderer->course_selector($nextstageurl, $wholecourse, $this->categorysearch, $this->coursesearch, $currentcourse); }
public function test_get_restore_content_dir() { global $CFG; $this->resetAfterTest(true); $this->setAdminUser(); $c1 = $this->getDataGenerator()->create_course(); $c2 = $this->getDataGenerator()->create_course((object) array('shortname' => 'Yay')); // Creating backup file. $bc = new backup_controller(backup::TYPE_1COURSE, $c1->id, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_GENERAL, 2); $bc->execute_plan(); $result = $bc->get_results(); $this->assertTrue(isset($result['backup_destination'])); $c1backupfile = $result['backup_destination']->copy_content_to_temp(); $bc->destroy(); unset($bc); // File logging is a mess, we can only try to rely on gc to close handles. // Creating backup file. $bc = new backup_controller(backup::TYPE_1COURSE, $c2->id, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_GENERAL, 2); $bc->execute_plan(); $result = $bc->get_results(); $this->assertTrue(isset($result['backup_destination'])); $c2backupfile = $result['backup_destination']->copy_content_to_temp(); $bc->destroy(); unset($bc); // File logging is a mess, we can only try to rely on gc to close handles. $oldcfg = isset($CFG->keeptempdirectoriesonbackup) ? $CFG->keeptempdirectoriesonbackup : false; $CFG->keeptempdirectoriesonbackup = true; // Checking restore dir. $dir = tool_uploadcourse_helper::get_restore_content_dir($c1backupfile, null); $bcinfo = backup_general_helper::get_backup_information($dir); $this->assertEquals($bcinfo->original_course_id, $c1->id); $this->assertEquals($bcinfo->original_course_fullname, $c1->fullname); // Do it again, it should be the same directory. $dir2 = tool_uploadcourse_helper::get_restore_content_dir($c1backupfile, null); $this->assertEquals($dir, $dir2); // Get the second course. $dir = tool_uploadcourse_helper::get_restore_content_dir($c2backupfile, null); $bcinfo = backup_general_helper::get_backup_information($dir); $this->assertEquals($bcinfo->original_course_id, $c2->id); $this->assertEquals($bcinfo->original_course_fullname, $c2->fullname); // Checking with a shortname. $dir = tool_uploadcourse_helper::get_restore_content_dir(null, $c1->shortname); $bcinfo = backup_general_helper::get_backup_information($dir); $this->assertEquals($bcinfo->original_course_id, $c1->id); $this->assertEquals($bcinfo->original_course_fullname, $c1->fullname); // Do it again, it should be the same directory. $dir2 = tool_uploadcourse_helper::get_restore_content_dir(null, $c1->shortname); $this->assertEquals($dir, $dir2); // Get the second course. $dir = tool_uploadcourse_helper::get_restore_content_dir(null, $c2->shortname); $bcinfo = backup_general_helper::get_backup_information($dir); $this->assertEquals($bcinfo->original_course_id, $c2->id); $this->assertEquals($bcinfo->original_course_fullname, $c2->fullname); // Get a course that does not exist. $errors = array(); $dir = tool_uploadcourse_helper::get_restore_content_dir(null, 'DoesNotExist', $errors); $this->assertFalse($dir); $this->assertArrayHasKey('coursetorestorefromdoesnotexist', $errors); // Trying again without caching. $CFG->keeptempdirectoriesonbackup is required for caching. $CFG->keeptempdirectoriesonbackup = false; // Checking restore dir. $dir = tool_uploadcourse_helper::get_restore_content_dir($c1backupfile, null); $dir2 = tool_uploadcourse_helper::get_restore_content_dir($c1backupfile, null); $this->assertNotEquals($dir, $dir2); // Checking with a shortname. $dir = tool_uploadcourse_helper::get_restore_content_dir(null, $c1->shortname); $dir2 = tool_uploadcourse_helper::get_restore_content_dir(null, $c1->shortname); $this->assertNotEquals($dir, $dir2); // Get a course that does not exist. $errors = array(); $dir = tool_uploadcourse_helper::get_restore_content_dir(null, 'DoesNotExist', $errors); $this->assertFalse($dir); $this->assertArrayHasKey('coursetorestorefromdoesnotexist', $errors); $dir2 = tool_uploadcourse_helper::get_restore_content_dir(null, 'DoesNotExist', $errors); $this->assertEquals($dir, $dir2); $CFG->keeptempdirectoriesonbackup = $oldcfg; }
/** * * @global moodle_database $DB * @param core_backup_renderer $renderer * @return string */ public function display($renderer) { global $DB, $USER, $PAGE; $format = backup_general_helper::detect_backup_format($this->filepath); if ($format !== 'moodle2') { return $renderer->invalid_format($format); } $this->details = backup_general_helper::get_backup_information($this->filepath); $url = new moodle_url('/backup/restore.php', array('contextid' => $this->contextid, 'filepath' => $this->filepath, 'stage' => restore_ui::STAGE_SETTINGS)); $context = get_context_instance_by_id($this->contextid); $currentcourse = $context->contextlevel == CONTEXT_COURSE && has_capability('moodle/restore:restorecourse', $context) ? $context->instanceid : false; $html = $renderer->course_selector($url, $this->details, $this->categorysearch, $this->coursesearch, $currentcourse); return $html; }
/** * Given one component/filearea/context and * optionally one source itemname to match itemids * put the corresponding files in the pool * * If you specify a progress reporter, it will get called once per file with * indeterminate progress. * * @param string $basepath the full path to the root of unzipped backup file * @param string $restoreid the restore job's identification * @param string $component * @param string $filearea * @param int $oldcontextid * @param int $dfltuserid default $file->user if the old one can't be mapped * @param string|null $itemname * @param int|null $olditemid * @param int|null $forcenewcontextid explicit value for the new contextid (skip mapping) * @param bool $skipparentitemidctxmatch * @param \core\progress\base $progress Optional progress reporter * @return array of result object */ public static function send_files_to_pool($basepath, $restoreid, $component, $filearea, $oldcontextid, $dfltuserid, $itemname = null, $olditemid = null, $forcenewcontextid = null, $skipparentitemidctxmatch = false, \core\progress\base $progress = null) { global $DB, $CFG; $backupinfo = backup_general_helper::get_backup_information(basename($basepath)); $includesfiles = $backupinfo->include_files; $results = array(); if ($forcenewcontextid) { // Some components can have "forced" new contexts (example: questions can end belonging to non-standard context mappings, // with questions originally at system/coursecat context in source being restored to course context in target). So we need // to be able to force the new contextid $newcontextid = $forcenewcontextid; } else { // Get new context, must exist or this will fail $newcontextrecord = self::get_backup_ids_record($restoreid, 'context', $oldcontextid); if (!$newcontextrecord || !$newcontextrecord->newitemid) { throw new restore_dbops_exception('unknown_context_mapping', $oldcontextid); } $newcontextid = $newcontextrecord->newitemid; } // Sometimes it's possible to have not the oldcontextids stored into backup_ids_temp->parentitemid // columns (because we have used them to store other information). This happens usually with // all the question related backup_ids_temp records. In that case, it's safe to ignore that // matching as far as we are always restoring for well known oldcontexts and olditemids $parentitemctxmatchsql = ' AND i.parentitemid = f.contextid '; if ($skipparentitemidctxmatch) { $parentitemctxmatchsql = ''; } // Important: remember how files have been loaded to backup_files_temp // - info: contains the whole original object (times, names...) // (all them being original ids as loaded from xml) // itemname = null, we are going to match only by context, no need to use itemid (all them are 0) if ($itemname == null) { $sql = "SELECT id AS bftid, contextid, component, filearea, itemid, itemid AS newitemid, info\n FROM {backup_files_temp}\n WHERE backupid = ?\n AND contextid = ?\n AND component = ?\n AND filearea = ?"; $params = array($restoreid, $oldcontextid, $component, $filearea); // itemname not null, going to join with backup_ids to perform the old-new mapping of itemids } else { $sql = "SELECT f.id AS bftid, f.contextid, f.component, f.filearea, f.itemid, i.newitemid, f.info\n FROM {backup_files_temp} f\n JOIN {backup_ids_temp} i ON i.backupid = f.backupid\n {$parentitemctxmatchsql}\n AND i.itemid = f.itemid\n WHERE f.backupid = ?\n AND f.contextid = ?\n AND f.component = ?\n AND f.filearea = ?\n AND i.itemname = ?"; $params = array($restoreid, $oldcontextid, $component, $filearea, $itemname); if ($olditemid !== null) { // Just process ONE olditemid intead of the whole itemname $sql .= ' AND i.itemid = ?'; $params[] = $olditemid; } } $fs = get_file_storage(); // Get moodle file storage $basepath = $basepath . '/files/'; // Get backup file pool base // Report progress before query. if ($progress) { $progress->progress(); } $rs = $DB->get_recordset_sql($sql, $params); foreach ($rs as $rec) { // Report progress each time around loop. if ($progress) { $progress->progress(); } $file = (object) backup_controller_dbops::decode_backup_temp_info($rec->info); // ignore root dirs (they are created automatically) if ($file->filepath == '/' && $file->filename == '.') { continue; } // set the best possible user $mappeduser = self::get_backup_ids_record($restoreid, 'user', $file->userid); $mappeduserid = !empty($mappeduser) ? $mappeduser->newitemid : $dfltuserid; // dir found (and not root one), let's create it if ($file->filename == '.') { $fs->create_directory($newcontextid, $component, $filearea, $rec->newitemid, $file->filepath, $mappeduserid); continue; } // The file record to restore. $file_record = array('contextid' => $newcontextid, 'component' => $component, 'filearea' => $filearea, 'itemid' => $rec->newitemid, 'filepath' => $file->filepath, 'filename' => $file->filename, 'timecreated' => $file->timecreated, 'timemodified' => $file->timemodified, 'userid' => $mappeduserid, 'source' => $file->source, 'author' => $file->author, 'license' => $file->license, 'sortorder' => $file->sortorder); if (empty($file->repositoryid)) { // If contenthash is empty then gracefully skip adding file. if (empty($file->contenthash)) { $result = new stdClass(); $result->code = 'file_missing_in_backup'; $result->message = sprintf('missing file (%s) contenthash in backup for component %s', $file->filename, $component); $result->level = backup::LOG_WARNING; $results[] = $result; continue; } // this is a regular file, it must be present in the backup pool $backuppath = $basepath . backup_file_manager::get_backup_content_file_location($file->contenthash); // Some file types do not include the files as they should already be // present. We still need to create entries into the files table. if ($includesfiles) { // The file is not found in the backup. if (!file_exists($backuppath)) { $results[] = self::get_missing_file_result($file); continue; } // create the file in the filepool if it does not exist yet if (!$fs->file_exists($newcontextid, $component, $filearea, $rec->newitemid, $file->filepath, $file->filename)) { // If no license found, use default. if ($file->license == null) { $file->license = $CFG->sitedefaultlicense; } $fs->create_file_from_pathname($file_record, $backuppath); } } else { // This backup does not include the files - they should be available in moodle filestorage already. // Create the file in the filepool if it does not exist yet. if (!$fs->file_exists($newcontextid, $component, $filearea, $rec->newitemid, $file->filepath, $file->filename)) { // Even if a file has been deleted since the backup was made, the file metadata will remain in the // files table, and the file will not be moved to the trashdir. // Files are not cleared from the files table by cron until several days after deletion. if ($foundfiles = $DB->get_records('files', array('contenthash' => $file->contenthash), '', '*', 0, 1)) { // Only grab one of the foundfiles - the file content should be the same for all entries. $foundfile = reset($foundfiles); $fs->create_file_from_storedfile($file_record, $foundfile->id); } else { // A matching existing file record was not found in the database. $results[] = self::get_missing_file_result($file); continue; } } } // store the the new contextid and the new itemid in case we need to remap // references to this file later $DB->update_record('backup_files_temp', array('id' => $rec->bftid, 'newcontextid' => $newcontextid, 'newitemid' => $rec->newitemid), true); } else { // this is an alias - we can't create it yet so we stash it in a temp // table and will let the final task to deal with it if (!$fs->file_exists($newcontextid, $component, $filearea, $rec->newitemid, $file->filepath, $file->filename)) { $info = new stdClass(); // oldfile holds the raw information stored in MBZ (including reference-related info) $info->oldfile = $file; // newfile holds the info for the new file_record with the context, user and itemid mapped $info->newfile = (object) $file_record; restore_dbops::set_backup_ids_record($restoreid, 'file_aliases_queue', $file->id, 0, null, $info); } } } $rs->close(); return $results; }
private function get_backupinfo($info) { $backupinfo = backup_general_helper::get_backup_information(basename($this->get_task()->get_basepath())); if (object_property_exists($backupinfo, $info)) { return $backupinfo->{$info}; } return false; }