/** * 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); }
/** * 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; }
/** * 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; }
/** * 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; }
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; }