/** * 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); }
/** * Converts the given directory with the backup into moodle2 format * * @param string $tempdir The directory to convert * @param string $format The current format, if already detected * @param base_logger|null if the conversion should be logged, use this logger * @throws convert_helper_exception * @return bool false if unable to find the conversion path, true otherwise */ public static function to_moodle2_format($tempdir, $format = null, $logger = null) { if (is_null($format)) { $format = backup_general_helper::detect_backup_format($tempdir); } // get the supported conversion paths from all available converters $converters = self::available_converters(); $descriptions = array(); foreach ($converters as $name) { $classname = "{$name}_converter"; if (!class_exists($classname)) { throw new convert_helper_exception('class_not_loaded', $classname); } if ($logger instanceof base_logger) { backup_helper::log('available converter', backup::LOG_DEBUG, $classname, 1, false, $logger); } $descriptions[$name] = call_user_func($classname . '::description'); } // choose the best conversion path for the given format $path = self::choose_conversion_path($format, $descriptions); if (empty($path)) { if ($logger instanceof base_logger) { backup_helper::log('unable to find the conversion path', backup::LOG_ERROR, null, 0, false, $logger); } return false; } if ($logger instanceof base_logger) { backup_helper::log('conversion path established', backup::LOG_INFO, implode(' => ', array_merge($path, array('moodle2'))), 0, false, $logger); } foreach ($path as $name) { if ($logger instanceof base_logger) { backup_helper::log('running converter', backup::LOG_INFO, $name, 0, false, $logger); } $converter = convert_factory::get_converter($name, $tempdir, $logger); $converter->convert(); } // make sure we ended with moodle2 format if (!self::detect_moodle2_format($tempdir)) { throw new convert_helper_exception('conversion_failed'); } return true; }
/** * Rename old backup files to current backup files. * * When added the setting 'backup_shortname' (MDL-28657) the backup file names did not contain the id of the course. * Further we fixed that behaviour by forcing the id to be always present in the file name (MDL-33812). * This function will explore the backup directory and attempt to rename the previously created files to include * the id in the name. Doing this will put them back in the process of deleting the excess backups for each course. * * This function manually recreates the file name, instead of using * {@link backup_plan_dbops::get_default_backup_filename()}, use it carefully if you're using it outside of the * usual upgrade process. * * @see backup_cron_automated_helper::remove_excess_backups() * @link http://tracker.moodle.org/browse/MDL-35116 * @return void * @since Moodle 2.4 */ function upgrade_rename_old_backup_files_using_shortname() { global $CFG; $dir = get_config('backup', 'backup_auto_destination'); $useshortname = get_config('backup', 'backup_shortname'); if (empty($dir) || !is_dir($dir) || !is_writable($dir)) { return; } require_once($CFG->dirroot.'/backup/util/includes/backup_includes.php'); $backupword = str_replace(' ', '_', core_text::strtolower(get_string('backupfilename'))); $backupword = trim(clean_filename($backupword), '_'); $filename = $backupword . '-' . backup::FORMAT_MOODLE . '-' . backup::TYPE_1COURSE . '-'; $regex = '#^'.preg_quote($filename, '#').'.*\.mbz$#'; $thirtyapril = strtotime('30 April 2012 00:00'); // Reading the directory. if (!$files = scandir($dir)) { return; } foreach ($files as $file) { // Skip directories and files which do not start with the common prefix. // This avoids working on files which are not related to this issue. if (!is_file($dir . '/' . $file) || !preg_match($regex, $file)) { continue; } // Extract the information from the XML file. try { $bcinfo = backup_general_helper::get_backup_information_from_mbz($dir . '/' . $file); } catch (backup_helper_exception $e) { // Some error while retrieving the backup informations, skipping... continue; } // Make sure this a course backup. if ($bcinfo->format !== backup::FORMAT_MOODLE || $bcinfo->type !== backup::TYPE_1COURSE) { continue; } // Skip the backups created before the short name option was initially introduced (MDL-28657). // This was integrated on the 2nd of May 2012. Let's play safe with timezone and use the 30th of April. if ($bcinfo->backup_date < $thirtyapril) { continue; } // Let's check if the file name contains the ID where it is supposed to be, if it is the case then // we will skip the file. Of course it could happen that the course ID is identical to the course short name // even though really unlikely, but then renaming this file is not necessary. If the ID is not found in the // file name then it was probably the short name which was used. $idfilename = $filename . $bcinfo->original_course_id . '-'; $idregex = '#^'.preg_quote($idfilename, '#').'.*\.mbz$#'; if (preg_match($idregex, $file)) { continue; } // Generating the file name manually. We do not use backup_plan_dbops::get_default_backup_filename() because // it will query the database to get some course information, and the course could not exist any more. $newname = $filename . $bcinfo->original_course_id . '-'; if ($useshortname) { $shortname = str_replace(' ', '_', $bcinfo->original_course_shortname); $shortname = core_text::strtolower(trim(clean_filename($shortname), '_')); $newname .= $shortname . '-'; } $backupdateformat = str_replace(' ', '_', get_string('backupnameformat', 'langconfig')); $date = userdate($bcinfo->backup_date, $backupdateformat, 99, false); $date = core_text::strtolower(trim(clean_filename($date), '_')); $newname .= $date; if (isset($bcinfo->root_settings['users']) && !$bcinfo->root_settings['users']) { $newname .= '-nu'; } else if (isset($bcinfo->root_settings['anonymize']) && $bcinfo->root_settings['anonymize']) { $newname .= '-an'; } $newname .= '.mbz'; // Final check before attempting the renaming. if ($newname == $file || file_exists($dir . '/' . $newname)) { continue; } @rename($dir . '/' . $file, $dir . '/' . $newname); } }
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; }
protected function define_execution() { // Get basepath $basepath = $this->get_basepath(); // Get the list of files in directory $filestemp = get_directory_list($basepath, '', false, true, true); $files = array(); foreach ($filestemp as $file) { // Add zip paths and fs paths to all them $files[$file] = $basepath . '/' . $file; } // Add the log file if exists $logfilepath = $basepath . '.log'; if (file_exists($logfilepath)) { $files['moodle_backup.log'] = $logfilepath; } // Calculate the zip fullpath (in OS temp area it's always backup.mbz) $zipfile = $basepath . '/backup.mbz'; // Get the zip packer $zippacker = get_file_packer('application/vnd.moodle.backup'); // Track overall progress for the 2 long-running steps (archive to // pathname, get backup information). $reporter = $this->task->get_progress(); $reporter->start_progress('backup_zip_contents', 2); // Zip files $result = $zippacker->archive_to_pathname($files, $zipfile, true, $this); // If any sub-progress happened, end it. if ($this->startedprogress) { $this->task->get_progress()->end_progress(); $this->startedprogress = false; } else { // No progress was reported, manually move it on to the next overall task. $reporter->progress(1); } // Something went wrong. if ($result === false) { @unlink($zipfile); throw new backup_step_exception('error_zip_packing', '', 'An error was encountered while trying to generate backup zip'); } // Read to make sure it is a valid backup. Refer MDL-37877 . Delete it, if found not to be valid. try { backup_general_helper::get_backup_information_from_mbz($zipfile, $this); } catch (backup_helper_exception $e) { @unlink($zipfile); throw new backup_step_exception('error_zip_packing', '', $e->debuginfo); } // If any sub-progress happened, end it. if ($this->startedprogress) { $this->task->get_progress()->end_progress(); $this->startedprogress = false; } else { $reporter->progress(2); } $reporter->end_progress(); }
public function calculate_checksum() { // Let's do it using name and tasks (settings are part of tasks) return md5($this->name . '-' . backup_general_helper::array_checksum_recursive($this->tasks)); }
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; }
/** * Removes excess backups from the external system and the local file system. * * The number of backups keep comes from $config->backup_auto_keep. * * @param stdClass $course object * @return bool */ public static function remove_excess_backups($course) { $config = get_config('backup'); $keep = (int) $config->backup_auto_keep; $storage = $config->backup_auto_storage; $dir = $config->backup_auto_destination; if ($keep == 0) { // Means keep all backup files. return true; } if (!file_exists($dir) || !is_dir($dir) || !is_writable($dir)) { $dir = null; } // Clean up excess backups in the course backup filearea. if ($storage == 0 || $storage == 2) { $fs = get_file_storage(); $context = context_course::instance($course->id); $component = 'backup'; $filearea = 'automated'; $itemid = 0; $files = array(); // Store all the matching files into timemodified => stored_file array. foreach ($fs->get_area_files($context->id, $component, $filearea, $itemid) as $file) { $files[$file->get_timemodified()] = $file; } if (count($files) <= $keep) { // There are less matching files than the desired number to keep there is nothing to clean up. return 0; } // Sort by keys descending (newer to older filemodified). krsort($files); $remove = array_splice($files, $keep); foreach ($remove as $file) { $file->delete(); } //mtrace('Removed '.count($remove).' old backup file(s) from the automated filearea'); } // Clean up excess backups in the specified external directory. if (!empty($dir) && ($storage == 1 || $storage == 2)) { // Calculate backup filename regex, ignoring the date/time/info parts that can be // variable, depending of languages, formats and automated backup settings. $filename = backup::FORMAT_MOODLE . '-' . backup::TYPE_1COURSE . '-' . $course->id . '-'; $regex = '#' . preg_quote($filename, '#') . '.*\\.mbz$#'; // Store all the matching files into filename => timemodified array. $files = array(); foreach (scandir($dir) as $file) { // Skip files not matching the naming convention. if (!preg_match($regex, $file, $matches)) { continue; } // Read the information contained in the backup itself. try { $bcinfo = backup_general_helper::get_backup_information_from_mbz($dir . '/' . $file); } catch (backup_helper_exception $e) { mtrace('Error: ' . $file . ' does not appear to be a valid backup (' . $e->errorcode . ')'); continue; } // Make sure this backup concerns the course and site we are looking for. if ($bcinfo->format === backup::FORMAT_MOODLE && $bcinfo->type === backup::TYPE_1COURSE && $bcinfo->original_course_id == $course->id && backup_general_helper::backup_is_samesite($bcinfo)) { $files[$file] = $bcinfo->backup_date; } } if (count($files) <= $keep) { // There are less matching files than the desired number to keep there is nothing to clean up. return 0; } // Sort by values descending (newer to older filemodified). arsort($files); $remove = array_splice($files, $keep); foreach (array_keys($remove) as $file) { unlink($dir . '/' . $file); } //mtrace('Removed '.count($remove).' old backup file(s) from external directory'); } return true; }
/** * 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; }
/** * Removes excess backups in the specified external directory from a specified course. * * @param stdClass $course Course object * @param int $now Starting time of the process * @return bool Whether or not backups are being removed */ protected static function remove_excess_backups_from_directory($course, $now) { $config = get_config('backup'); $dir = $config->backup_auto_destination; $isnotvaliddir = !file_exists($dir) || !is_dir($dir) || !is_writable($dir); if ($isnotvaliddir) { mtrace('Error: ' . $dir . ' does not appear to be a valid directory'); return false; } // Calculate backup filename regex, ignoring the date/time/info parts that can be // variable, depending of languages, formats and automated backup settings. $filename = backup::FORMAT_MOODLE . '-' . backup::TYPE_1COURSE . '-' . $course->id . '-'; $regex = '#' . preg_quote($filename, '#') . '.*\\.mbz$#'; // Store all the matching files into filename => timemodified array. $backupfiles = array(); foreach (scandir($dir) as $backupfile) { // Skip files not matching the naming convention. if (!preg_match($regex, $backupfile)) { continue; } // Read the information contained in the backup itself. try { $bcinfo = backup_general_helper::get_backup_information_from_mbz($dir . '/' . $backupfile); } catch (backup_helper_exception $e) { mtrace('Error: ' . $backupfile . ' does not appear to be a valid backup (' . $e->errorcode . ')'); continue; } // Make sure this backup concerns the course and site we are looking for. if ($bcinfo->format === backup::FORMAT_MOODLE && $bcinfo->type === backup::TYPE_1COURSE && $bcinfo->original_course_id == $course->id && backup_general_helper::backup_is_samesite($bcinfo)) { $backupfiles[$bcinfo->backup_date] = $backupfile; } } $backupstodelete = self::get_backups_to_delete($backupfiles, $now); if ($backupstodelete) { foreach ($backupstodelete as $backuptodelete) { unlink($dir . '/' . $backuptodelete); } mtrace('Deleted ' . count($backupstodelete) . ' old backup file(s) from external directory'); return true; } else { return false; } }
/** * Return an array of owners and a list of each course they are teachers of. * * @param object $obj course obj * @param string $folder name of folder (optional) * @return bool of courses that match the search */ protected function archivecourse($obj, $folder = false) { global $CFG; require_once $CFG->dirroot . '/backup/util/includes/backup_includes.php'; require_once $CFG->dirroot . '/backup/controller/backup_controller.class.php'; if (empty($CFG->siteadmins)) { // Should not happen on an ordinary site. return false; } else { $admin = get_admin(); } $coursetobackup = $obj["course"]->id; // Set this to one existing choice cmid in your dev site. $userdoingthebackup = $admin->id; // Set this to the id of your admin account. try { // Prepare path. $matchers = array('/\\s/', '/\\//'); $safeshort = preg_replace($matchers, '-', $obj["course"]->shortname); if (empty($obj["course"]->idnumber)) { $suffix = '-ID-' . $obj["course"]->id; } else { $suffix = '-ID-' . $obj["course"]->id . '-IDNUM-' . $obj["course"]->idnumber; } $archivefile = date("Y-m-d") . "{$suffix}-{$safeshort}.zip"; $archivepath = str_replace(str_split('\\/:*?"<>|'), '', get_config('tool_coursearchiver', 'coursearchiverpath')); // Check for custom folder. if (!empty($folder)) { $folder = str_replace(str_split('\\/:*?"<>|'), '', $folder); } // If no custom folder is given, use the current year. if (empty($folder)) { $folder = date('Y'); } // Final full path of file. $path = $CFG->dataroot . '/' . $archivepath . '/' . $folder; // If the path doesn't exist, make it so! if (!is_dir($path)) { umask(00); // Create the directory for CourseArchival. if (!mkdir($path, $CFG->directorypermissions, true)) { throw new Exception('Archive path could not be created'); } @chmod($path, $dirpermissions); } // Perform Backup. $bc = new backup_controller(backup::TYPE_1COURSE, $coursetobackup, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_AUTOMATED, $userdoingthebackup); $bc->execute_plan(); // Execute backup. $results = $bc->get_results(); // Get the file information needed. $config = get_config('backup'); $dir = $config->backup_auto_destination; // The backup file will have already been moved, so I have to find it. if (!empty($dir)) { // Calculate backup filename regex, ignoring the date/time/info parts that can be // variable, depending of languages, formats and automated backup settings. $filename = backup::FORMAT_MOODLE . '-' . backup::TYPE_1COURSE . '-' . $obj["course"]->id . '-'; $regex = '#' . preg_quote($filename, '#') . '.*\\.mbz#'; // Store all the matching files into filename => timemodified array. $files = array(); foreach (scandir($dir) as $file) { // Skip files not matching the naming convention. if (!preg_match($regex, $file, $matches)) { continue; } // Read the information contained in the backup itself. try { $bcinfo = backup_general_helper::get_backup_information_from_mbz($dir . '/' . $file); } catch (backup_helper_exception $e) { throw new Exception('Error: ' . $file . ' does not appear to be a valid backup (' . $e->errorcode . ')'); continue; } // Make sure this backup concerns the course and site we are looking for. if ($bcinfo->format === backup::FORMAT_MOODLE && $bcinfo->type === backup::TYPE_1COURSE && $bcinfo->original_course_id == $obj["course"]->id && backup_general_helper::backup_is_samesite($bcinfo)) { $files[$file] = $bcinfo->backup_date; } } // Sort by values descending (newer to older filemodified). arsort($files); foreach ($files as $filename => $backupdate) { // Make sure the backup is from today. if (date('m/d/Y', $backupdate) == date('m/d/Y')) { rename($dir . '/' . $filename, $path . '/' . $archivefile); } break; // Just the last backup...thanks! } } else { $file = $results['backup_destination']; if (!empty($file)) { $file->copy_content_to($path . '/' . $archivefile); } else { throw new Exception('Backup failed'); } } $bc->destroy(); unset($bc); if (file_exists($path . '/' . $archivefile)) { // Make sure file got moved. // Remove Course. delete_course($obj["course"]->id, false); } else { throw new Exception('Course archive file does not exist'); } } catch (Exception $e) { return false; } return true; }
public function calculate_checksum() { // Let's do it using name and settings and steps return md5($this->name . '-' . backup_general_helper::array_checksum_recursive($this->settings) . backup_general_helper::array_checksum_recursive($this->steps)); }
protected function backup_details_list($details) { $out = $this->backup_detail_display('backuptype', $details->type); $out .= $this->backup_detail_display('backupformat', $details->format); $out .= $this->backup_detail_display('backupmode', $details->mode); $out .= $this->backup_detail_item('backupdate', userdate($details->backup_date)); $out .= $this->backup_detail_extra_info('moodleversion', $details->moodle_release, $details->moodle_version); $out .= $this->backup_detail_extra_info('backupversion', $details->backup_release, $details->backup_version); $out .= $this->backup_detail_extra_info('originalwwwroot', $details->original_wwwroot, $details->original_site_identifier_hash); if (!empty($details->include_file_references_to_external_content)) { $message = ''; if (backup_general_helper::backup_is_samesite($details)) { $message = label::yes() . ' ' . get_string('filereferencessamesite', 'backup'); } else { $message = label::no() . ' ' . get_string('filereferencesnotsamesite', 'backup'); } $out .= $this->backup_detail_item('includefilereferences', $message); } return $out; }
/** * Displays the details of a backup file * * @param stdClass $details * @param moodle_url $nextstageurl * @return string */ public function backup_details($details, $nextstageurl) { $yestick = $this->output->pix_icon('i/valid', get_string('yes')); $notick = $this->output->pix_icon('i/invalid', get_string('no')); $html = html_writer::start_tag('div', array('class' => 'backup-restore')); $html .= html_writer::start_tag('div', array('class' => 'backup-section')); $html .= $this->output->heading(get_string('backupdetails', 'backup'), 2, array('class' => 'header')); $html .= $this->backup_detail_pair(get_string('backuptype', 'backup'), get_string('backuptype' . $details->type, 'backup')); $html .= $this->backup_detail_pair(get_string('backupformat', 'backup'), get_string('backupformat' . $details->format, 'backup')); $html .= $this->backup_detail_pair(get_string('backupmode', 'backup'), get_string('backupmode' . $details->mode, 'backup')); $html .= $this->backup_detail_pair(get_string('backupdate', 'backup'), userdate($details->backup_date)); $html .= $this->backup_detail_pair(get_string('moodleversion', 'backup'), html_writer::tag('span', $details->moodle_release, array('class' => 'moodle_release')) . html_writer::tag('span', '[' . $details->moodle_version . ']', array('class' => 'moodle_version sub-detail'))); $html .= $this->backup_detail_pair(get_string('backupversion', 'backup'), html_writer::tag('span', $details->backup_release, array('class' => 'moodle_release')) . html_writer::tag('span', '[' . $details->backup_version . ']', array('class' => 'moodle_version sub-detail'))); $html .= $this->backup_detail_pair(get_string('originalwwwroot', 'backup'), html_writer::tag('span', $details->original_wwwroot, array('class' => 'originalwwwroot')) . html_writer::tag('span', '[' . $details->original_site_identifier_hash . ']', array('class' => 'sitehash sub-detail'))); if (!empty($details->include_file_references_to_external_content)) { $message = ''; if (backup_general_helper::backup_is_samesite($details)) { $message = $yestick . ' ' . get_string('filereferencessamesite', 'backup'); } else { $message = $notick . ' ' . get_string('filereferencesnotsamesite', 'backup'); } $html .= $this->backup_detail_pair(get_string('includefilereferences', 'backup'), $message); } $html .= html_writer::end_tag('div'); $html .= html_writer::start_tag('div', array('class' => 'backup-section settings-section')); $html .= $this->output->heading(get_string('backupsettings', 'backup'), 2, array('class' => 'header')); foreach ($details->root_settings as $label => $value) { if ($label == 'filename' or $label == 'user_files') { continue; } $html .= $this->backup_detail_pair(get_string('rootsetting' . str_replace('_', '', $label), 'backup'), $value ? $yestick : $notick); } $html .= html_writer::end_tag('div'); if ($details->type === 'course') { $html .= html_writer::start_tag('div', array('class' => 'backup-section')); $html .= $this->output->heading(get_string('backupcoursedetails', 'backup'), 2, array('class' => 'header')); $html .= $this->backup_detail_pair(get_string('coursetitle', 'backup'), $details->course->title); $html .= $this->backup_detail_pair(get_string('courseid', 'backup'), $details->course->courseid); $html .= html_writer::start_tag('div', array('class' => 'backup-sub-section')); $html .= $this->output->heading(get_string('backupcoursesections', 'backup'), 3, array('class' => 'subheader')); foreach ($details->sections as $key => $section) { $included = $key . '_included'; $userinfo = $key . '_userinfo'; if ($section->settings[$included] && $section->settings[$userinfo]) { $value = get_string('sectionincanduser', 'backup'); } else { if ($section->settings[$included]) { $value = get_string('sectioninc', 'backup'); } else { continue; } } $html .= $this->backup_detail_pair(get_string('backupcoursesection', 'backup', $section->title), $value); $table = null; foreach ($details->activities as $activitykey => $activity) { if ($activity->sectionid != $section->sectionid) { continue; } if (empty($table)) { $table = new html_table(); $table->head = array(get_string('module', 'backup'), get_string('title', 'backup'), get_string('userinfo', 'backup')); $table->colclasses = array('modulename', 'moduletitle', 'userinfoincluded'); $table->align = array('left', 'left', 'center'); $table->attributes = array('class' => 'activitytable generaltable'); $table->data = array(); } $name = get_string('pluginname', $activity->modulename); $icon = new pix_icon('icon', $name, $activity->modulename, array('class' => 'iconlarge icon-pre')); $table->data[] = array($this->output->render($icon) . $name, $activity->title, $activity->settings[$activitykey . '_userinfo'] ? $yestick : $notick); } if (!empty($table)) { $html .= $this->backup_detail_pair(get_string('sectionactivities', 'backup'), html_writer::table($table)); } } $html .= html_writer::end_tag('div'); $html .= html_writer::end_tag('div'); } $html .= $this->output->single_button($nextstageurl, get_string('continue'), 'post'); $html .= html_writer::end_tag('div'); return $html; }
/** * * @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; }
/** * 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); }
/** * Restore one 1-course backup */ protected static function build_course_plan($controller, $courseid) { $plan = $controller->get_plan(); $info = $controller->get_info(); // Add the course task, responsible for restoring // all the course related information $task = restore_factory::get_restore_course_task($info->course, $courseid); $plan->add_task($task); $controller->get_progress()->progress(); // For the given course path, add as many block tasks as necessary // TODO: Add blocks, we need to introspect xml here $blocks = backup_general_helper::get_blocks_from_path($task->get_taskbasepath()); foreach ($blocks as $basepath => $name) { if ($task = restore_factory::get_restore_block_task($name, $basepath)) { $plan->add_task($task); $controller->get_progress()->progress(); } else { // TODO: Debug information about block not supported } } // For the given course, add as many section tasks as necessary foreach ($info->sections as $sectionid => $section) { self::build_section_plan($controller, $sectionid); } }
public function calculate_checksum() { // Reset current checksum to take it out from calculations! $this->checksum = ''; // Init checksum $tempchecksum = md5('backupid-' . $this->backupid . 'type-' . $this->type . 'id-' . $this->id . 'format-' . $this->format . 'interactive-' . $this->interactive . 'mode-' . $this->mode . 'userid-' . $this->userid . 'operation-' . $this->operation . 'status-' . $this->status . 'execution-' . $this->execution . 'plan-' . backup_general_helper::array_checksum_recursive(array($this->plan)) . 'destination-' . backup_general_helper::array_checksum_recursive(array($this->destination)) . 'logger-' . backup_general_helper::array_checksum_recursive(array($this->logger))); $this->log('calculating controller checksum', backup::LOG_DEBUG, $tempchecksum); return $tempchecksum; }
protected function define_execution() { // Get basepath $basepath = $this->get_basepath(); // Get the list of files in directory $filestemp = get_directory_list($basepath, '', false, true, true); $files = array(); foreach ($filestemp as $file) { // Add zip paths and fs paths to all them $files[$file] = $basepath . '/' . $file; } // Add the log file if exists $logfilepath = $basepath . '.log'; if (file_exists($logfilepath)) { $files['moodle_backup.log'] = $logfilepath; } // Calculate the zip fullpath (in OS temp area it's always backup.mbz) $zipfile = $basepath . '/backup.mbz'; // Get the zip packer $zippacker = get_file_packer('application/zip'); // Zip files $result = $zippacker->archive_to_pathname($files, $zipfile, true, $this); // Something went wrong. if ($result === false) { @unlink($zipfile); throw new backup_step_exception('error_zip_packing', '', 'An error was encountered while trying to generate backup zip'); } // Read to make sure it is a valid backup. Refer MDL-37877 . Delete it, if found not to be valid. try { backup_general_helper::get_backup_information_from_mbz($zipfile); } catch (backup_helper_exception $e) { @unlink($zipfile); throw new backup_step_exception('error_zip_packing', '', $e->debuginfo); } }