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