Created
October 1, 2019 18:39
-
-
Save ericmerrill/234e18c0022d4bb501cdcfe673c53eb2 to your computer and use it in GitHub Desktop.
Moodle Backup and Delete Scripts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| define('CLI_SCRIPT', true); | |
| require(dirname(__FILE__).'/config.php'); | |
| require_once($CFG->libdir.'/clilib.php'); // cli only functions | |
| // now get cli options | |
| list($options, $unrecognized) = cli_get_params(array('help' => false, | |
| 'course' => false, | |
| 'cat' => false, | |
| 'check-only' => false, | |
| 'check-checksum' => false, | |
| 'start' => 0, | |
| 'limit' => 0, | |
| 'no-users' => false, | |
| 'force' => false, | |
| 'directory' => false), | |
| array('h' => 'help', | |
| 'C' => 'course', | |
| 'c' => 'cat', | |
| 's' => 'start', | |
| 'l' => 'limit', | |
| 'n' => 'no-users', | |
| 'f' => 'force')); | |
| if ($unrecognized) { | |
| $unrecognized = implode("\n ", $unrecognized); | |
| cli_error(get_string('cliunknowoption', 'admin', $unrecognized)); | |
| } | |
| if (!$options['course'] && !$options['cat']) { | |
| echo "Need to specify a category or course.\n\n"; | |
| $options['help'] = true; | |
| } | |
| if ($options['help']) { | |
| $help = | |
| "Execute archive backups. | |
| This script executes archive backups. | |
| Options: | |
| -C, --course Course ID | |
| -c, --cat Category ID | |
| -s, --start Limit start | |
| -l, --limit Limit count | |
| -n, --no-users Create backups without user data | |
| -f, --force Force backup, even if file already exists | |
| --check-only Perform the existing file check only. Don't move or recreate. | |
| --check-checksum When using a .sha1 checkfile to confirm backup has been validated, confirm the checksum value. | |
| --directory Set the absolute directory to place backups in | |
| -h, --help Print out this help | |
| Example: | |
| \$sudo -u www-data /usr/bin/php local/elis/cli/backup_courses.php | |
| "; | |
| echo $help; | |
| die; | |
| } | |
| if (CLI_MAINTENANCE) { | |
| echo "CLI maintenance mode active, backup execution suspended.\n"; | |
| exit(1); | |
| } | |
| if (moodle_needs_upgrading()) { | |
| echo "Moodle upgrade pending, backup execution suspended.\n"; | |
| exit(1); | |
| } | |
| require_once($CFG->libdir.'/adminlib.php'); | |
| require_once($CFG->libdir.'/gradelib.php'); | |
| require_once($CFG->dirroot.'/backup/util/includes/backup_includes.php'); | |
| require_once($CFG->dirroot.'/backup/util/helper/backup_cron_helper.class.php'); | |
| $DIRECTORY = null; | |
| $starttime = time(); | |
| $thisweek = date('Y-m-d'); | |
| if ($options['course']) { | |
| if (!is_numeric($options['course'])) { | |
| echo "ERROR: Course ID needs to be a number.\n"; | |
| exit(1); | |
| } | |
| $courses = $DB->get_records('course', array('id' => $options['course'])); | |
| } else if ($options['cat']) { | |
| if (!is_numeric($options['cat'])) { | |
| echo "ERROR: Category ID needs to be a number.\n"; | |
| exit(1); | |
| } | |
| if (!is_numeric($options['start'])) { | |
| echo "ERROR: Limit start needs to be a number.\n"; | |
| exit(1); | |
| } | |
| if (!is_numeric($options['limit'])) { | |
| echo "ERROR: Limit count needs to be a number.\n"; | |
| exit(1); | |
| } | |
| $courses = $DB->get_records('course', array('category' => $options['cat']), 'id ASC', '*', $options['start'], $options['limit']); | |
| } | |
| if (!$courses) { | |
| echo "ERROR: No courses found.\n"; | |
| exit(1); | |
| } | |
| $users = true; | |
| if ($options['no-users']) { | |
| $users = false; | |
| } | |
| setup_config(); | |
| $basepath = get_config('backup', 'backup_auto_destination'); | |
| $num = 1; | |
| $count = count($courses); | |
| $lf = \core\lock\lock_config::get_lock_factory('local_elis_course_backup'); | |
| if ($options['check-only']) { | |
| echo "Performing a check of {$basepath}\n"; | |
| foreach ($courses as $course) { | |
| $key = 'course:'.$course->id; | |
| $lock = $lf->get_lock($key, 0); | |
| echo "$num of $count, course ".$course->id.". "; | |
| if ($lock === false) { | |
| echo "Lock held elsewhere."; | |
| } else { | |
| $existing = find_existing_filename($course); | |
| if ($existing !== false) { | |
| echo "{$existing} found. "; | |
| if (check_file_exists($existing)) { | |
| echo "Check file already exists."; | |
| } else { | |
| $good = check_backup_good($existing); | |
| if ($good) { | |
| echo "File is good."; | |
| create_check_file($existing); | |
| } else { | |
| echo "ERROR: Backup file not good."; | |
| } | |
| } | |
| } else { | |
| echo "ERROR: No backup found. "; | |
| } | |
| $lock->release(); | |
| } | |
| echo "\n"; | |
| $num++; | |
| } | |
| exit(); | |
| } | |
| echo "Saving to $basepath\n"; | |
| foreach ($courses as $course) { | |
| echo "$num of $count, course ".$course->id.". "; | |
| $key = 'course:'.$course->id; | |
| $lock = $lf->get_lock($key, 0); | |
| if ($lock === false) { | |
| echo "Course lock in place, being backed up elsewhere."; | |
| } else if (($filename = course_exists($course)) !== false) { | |
| echo "Course already exists as ".$filename."."; | |
| $lock->release(); | |
| } else { | |
| echo "Backing up "; | |
| if ($status = backup_cron_automated_helper::launch_automated_backup($course, $starttime, 2)) { | |
| $time = time(); | |
| $suffix = '-nu'; | |
| if ($users) { | |
| $suffix = ''; | |
| } | |
| for ($i = 0; $i <= 1200; $i = $i + 60) { | |
| $orgfile = $basepath.'/backup-moodle2-course-'.$course->id.'-'. | |
| date('Ymd', $time-$i).'-'.date('Hi', $time-$i).$suffix.'.mbz'; | |
| if (file_exists($orgfile)) { | |
| break; | |
| } | |
| } | |
| $basename = get_filename_base($course); | |
| $name = $basename."-".$thisweek.".mbz"; | |
| $newfile = $basepath.'/'.$name; | |
| if (rename($orgfile, $newfile)) { | |
| echo $name."."; | |
| if (!check_backup_good($name)) { | |
| // This shouldn't happen. The file we just made looks like it's bad. | |
| echo "ERROR: The file created seems like it is bad!"; | |
| $badfile = $basepath.'/bad-created-'.$name; | |
| rename($newfile, $badfile); | |
| } | |
| } else { | |
| echo "Error renaming."; | |
| } | |
| } else { | |
| echo "Error creating backup."; | |
| } | |
| // Just in case there was some old check file. Remove it. | |
| $checkfile = $basename."-".$thisweek.".sha1"; | |
| if (file_exists($basepath.'/'.$checkfile)) { | |
| unlink($basepath.'/'.$checkfile); | |
| } | |
| $lock->release(); | |
| } | |
| print "\n"; | |
| $num++; | |
| } | |
| function course_exists($course) { | |
| global $basepath, $options; | |
| if ($options['force']) { | |
| return false; | |
| } | |
| $filename = find_existing_filename($course); | |
| if (empty($filename)) { | |
| return false; | |
| } | |
| if (check_file_exists($filename)) { | |
| // The file exists and was already checked. | |
| echo "Checkfile used. "; | |
| return $filename; | |
| } else if (check_backup_good($filename)) { | |
| // Good file, we can use it. | |
| // This is more than the first check, so we can mark it as checked. | |
| echo "File is good. "; | |
| create_check_file($filename); | |
| return $filename; | |
| } else { | |
| // This means we suspect the existing file is bad. Move it, and we will try again. | |
| echo "Moving existing file. "; | |
| $orgfile = $basepath.'/'.$filename; | |
| $newfile = $basepath.'/bad-'.$filename; | |
| rename($orgfile, $newfile); | |
| return false; | |
| } | |
| return false; | |
| } | |
| function find_existing_filename($course) { | |
| global $basepath, $options, $DIRECTORY; | |
| $name = get_filename_base($course); | |
| if (is_null($DIRECTORY)) { | |
| reload_directory(); | |
| } | |
| $filename = search_directory_for_file($name); | |
| return $filename; | |
| } | |
| function search_directory_for_file($name, $extension = 'mbz') { | |
| $result = search_directory_for_file_inner($name, $extension); | |
| if ($result !== false) { | |
| return $result; | |
| } | |
| reload_directory(); | |
| return search_directory_for_file_inner($name, $extension); | |
| } | |
| function search_directory_for_file_inner($name, $extension = 'mbz') { | |
| global $DIRECTORY; | |
| foreach ($DIRECTORY as $file) { | |
| $ext = $file->getExtension(); | |
| if (strcmp($ext, $extension) !== 0) { | |
| continue; | |
| } | |
| $filename = $file->getFilename(); | |
| if (strncmp($filename, $name, strlen($name)) === 0) { | |
| return $filename; | |
| } | |
| } | |
| return false; | |
| } | |
| function reload_directory() { | |
| global $basepath, $DIRECTORY; | |
| $DIRECTORY = new DirectoryIterator($basepath); | |
| if (empty($DIRECTORY)) { | |
| echo "ERROR: Cannot open {$basepath}."; | |
| exit(1); | |
| } | |
| } | |
| function find_existing_filename_old($course) { | |
| global $basepath, $options; | |
| $existing = get_directory_list($basepath, '', false, false, true); | |
| if (!($idnumber = $course->idnumber)) { | |
| $idnumber = "noidnum"; | |
| } | |
| $name = $idnumber."-".$course->id; | |
| foreach ($existing as $filename) { | |
| if (strncmp($filename, $name, strlen($name)) === 0 && strncmp('mbz', substr($filename, -3), 3) === 0) { | |
| return $filename; | |
| } | |
| } | |
| } | |
| function check_backup_good($backupfile) { | |
| global $basepath, $options, $users; | |
| // A collection of files we expect to be in the complete archive. | |
| // 1 means any filetype, 2 means only in files with user data. | |
| $expected = ['.ARCHIVE_INDEX' => 1, | |
| 'badges.xml' => 2, | |
| 'completion.xml' => 1, | |
| 'course/calendar.xml' => 1, | |
| 'course/course.xml' => 1, | |
| 'course/comments.xml' => 2, | |
| 'course/enrolments.xml' => 1, | |
| 'course/filters.xml' => 1, | |
| 'course/inforef.xml' => 1, | |
| 'course/logs.xml' => 2, | |
| 'course/logstores.xml' => 2, | |
| 'course/roles.xml' => 1, | |
| 'files.xml' => 1, | |
| 'grade_history.xml' => 1, | |
| 'gradebook.xml' => 1, | |
| 'groups.xml' => 1, | |
| 'moodle_backup.xml' => 1, | |
| 'outcomes.xml' => 1, | |
| 'questions.xml' => 1, | |
| 'roles.xml' => 1, | |
| 'scales.xml' => 1, | |
| 'sections/' => 1, | |
| 'users.xml' => 2, | |
| 'moodle_backup.log' => 1]; | |
| if (!$users) { | |
| foreach ($expected as $key => $value) { | |
| if ($value == 2) { | |
| unset($expected[$key]); | |
| } | |
| } | |
| } | |
| $command = 'tar -tf '.escapeshellarg($basepath.'/'.$backupfile).' 2>&1'; | |
| exec($command, $output, $returncode); | |
| if ($returncode !== 0) { | |
| echo "ERROR: Bad return code reading file at ".$basepath.'/'.$backupfile.". "; | |
| return false; | |
| } | |
| if (empty($output) || count($output) < 10) { | |
| // BAD! | |
| echo "ERROR: Bad existing file found at ".$basepath.'/'.$backupfile.". "; | |
| return false; | |
| } | |
| // We process through each line of the result, removing it from the expected array. | |
| foreach ($output as $file) { | |
| unset($expected[$file]); | |
| } | |
| // If this array is empty when we are done, then we know we got all the files we expected. | |
| if (empty($expected)) { | |
| return true; | |
| } else { | |
| // There were expected files not found, so we are going to flag this file. | |
| echo "ERROR: Bad existing file found at ".$basepath.'/'.$backupfile.". Missing files: \n " | |
| .implode(array_keys($expected), "\n "); | |
| return false; | |
| } | |
| } | |
| function create_check_file($filename) { | |
| global $basepath; | |
| $sha = sha1_file($basepath.'/'.$filename); | |
| $newname = substr($filename, 0, -3).'sha1'; | |
| file_put_contents($basepath.'/'.$newname, $sha); | |
| //touch($basepath.'/'.$newname); | |
| } | |
| function check_file_exists($filename) { | |
| global $basepath, $options; | |
| $checkname = substr($filename, 0, -3).'sha1'; | |
| $checkpath = $basepath.'/'.$checkname; | |
| $exists = search_directory_for_file($checkname, 'sha1'); | |
| if (!$exists) { | |
| return false; | |
| } | |
| if (empty($options['check-checksum'])) { | |
| return true; | |
| } | |
| // Check the contents of the file. | |
| $fh = fopen($checkpath, 'r'); | |
| if (empty($fh)) { | |
| return false; | |
| } | |
| $checksha = trim(fread($fh, 45)); | |
| fclose($fp); | |
| if (empty($checksha) || strlen($checksha) !== 40) { | |
| unlink($checkpath); | |
| return false; | |
| } | |
| $newsha = sha1_file($basepath.'/'.$filename); | |
| if (strcmp($checksha, $newsha) === 0) { | |
| // This means the file was good with a matching sha. | |
| return true; | |
| } | |
| // This means the sha checksums didn't match. We should delete it. | |
| unlink($checkpath); | |
| return false; | |
| } | |
| function get_filename_base($course) { | |
| global $users; | |
| if (!($idnumber = $course->idnumber)) { | |
| $idnumber = "noidnum"; | |
| } | |
| $name = $idnumber."-".$course->id; | |
| if ($users) { | |
| $name .= '-userdata'; | |
| } else { | |
| $name .= '-nousers'; | |
| } | |
| return $name; | |
| } | |
| // Force the config for what we are doing. | |
| function setup_config() { | |
| global $CFG, $users, $options; | |
| if ($users) { | |
| $settings = ['backup_auto_storage' => 1, | |
| 'backup_auto_users' => true, | |
| 'backup_auto_role_assignments' => true, | |
| 'backup_auto_activities' => true, | |
| 'backup_auto_blocks' => true, | |
| 'backup_auto_filters' => true, | |
| 'backup_auto_comments' => true, | |
| 'backup_auto_badges' => true, | |
| 'backup_auto_calendarevents' => true, | |
| 'backup_auto_userscompletion' => true, | |
| 'backup_auto_logs' => true, | |
| 'backup_auto_histories' => true, | |
| 'backup_auto_questionbank' => true, | |
| 'backup_auto_groups' => true, | |
| 'backup_auto_competencies' => true]; | |
| } else { | |
| $settings = ['backup_auto_storage' => 1, | |
| 'backup_auto_users' => false, | |
| 'backup_auto_role_assignments' => false, | |
| 'backup_auto_activities' => true, | |
| 'backup_auto_blocks' => true, | |
| 'backup_auto_filters' => true, | |
| 'backup_auto_comments' => false, | |
| 'backup_auto_badges' => false, | |
| 'backup_auto_calendarevents' => true, | |
| 'backup_auto_userscompletion' => false, | |
| 'backup_auto_logs' => false, | |
| 'backup_auto_histories' => false, | |
| 'backup_auto_questionbank' => true, | |
| 'backup_auto_groups' => true, | |
| 'backup_auto_competencies' => true]; | |
| } | |
| if ($options['directory']) { | |
| $dir = $options['directory']; | |
| if (strpos($dir, '/') !== 0) { | |
| echo "ERROR: Directory {$dir} is not absolue - must start with a slash.\n"; | |
| exit(1); | |
| } | |
| if (strrpos($dir, '/') === strlen($dir)-1) { | |
| $dir = substr($dir, 0, strlen($dir)-1); | |
| } | |
| if (!is_dir($dir) || !is_writable($dir)) { | |
| echo "ERROR: Directory {$dir} does not exist or is not writable.\n"; | |
| exit(1); | |
| } | |
| $settings['backup_auto_destination'] = $dir; | |
| } | |
| if (isset($CFG->forced_plugin_settings['backup'])) { | |
| $CFG->forced_plugin_settings['backup'] = array_merge($CFG->forced_plugin_settings['backup'], $settings); | |
| } else { | |
| $CFG->forced_plugin_settings['backup'] = $settings; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| define('CLI_SCRIPT', true); | |
| require(dirname(__FILE__).'/config.php'); | |
| require_once($CFG->libdir.'/clilib.php'); // cli only functions | |
| // now get cli options | |
| list($options, $unrecognized) = cli_get_params(array('help' => false, 'course' => false, 'cat' => false, 'start' => 0, 'limit' => 0), | |
| array('h' => 'help', 'C' => 'course', 'c' => 'cat', 's' => 'start', 'l' => 'limit')); | |
| if ($unrecognized) { | |
| $unrecognized = implode("\n ", $unrecognized); | |
| cli_error(get_string('cliunknowoption', 'admin', $unrecognized)); | |
| } | |
| if (!$options['course'] && !$options['cat']) { | |
| echo "Need to specify a category or course.\n\n"; | |
| $options['help'] = true; | |
| } | |
| if ($options['help']) { | |
| $help = | |
| "Bulk delete courses - USE WITH CAUTION! | |
| This script bulk deleted courses from Moodle. | |
| Options: | |
| -C, --course Course ID | |
| -c, --cat Category ID | |
| -s, --start Limit start | |
| -l, --limit Limit count | |
| -h, --help Print out this help | |
| Example: | |
| \$sudo -u www-data /usr/bin/php local/elis/cli/delete_category.php | |
| "; | |
| echo $help; | |
| die; | |
| } | |
| if (CLI_MAINTENANCE) { | |
| echo "CLI maintenance mode active, backup execution suspended.\n"; | |
| exit(1); | |
| } | |
| if (moodle_needs_upgrading()) { | |
| echo "Moodle upgrade pending, backup execution suspended.\n"; | |
| exit(1); | |
| } | |
| raise_memory_limit(MEMORY_HUGE); | |
| //$course = $DB->get_record('course', array('id' => 13)); | |
| require_once($CFG->libdir.'/adminlib.php'); | |
| require_once($CFG->libdir.'/gradelib.php'); | |
| require_once($CFG->dirroot.'/backup/util/includes/backup_includes.php'); | |
| require_once($CFG->dirroot.'/backup/util/helper/backup_cron_helper.class.php'); | |
| $starttime = time(); | |
| cron_setup_user(); | |
| if ($options['course']) { | |
| if (!is_numeric($options['course'])) { | |
| echo "Course ID needs to be a number.\n"; | |
| exit(1); | |
| } | |
| $courseids = $DB->get_records('course', array('id' => $options['course']), '', 'id'); | |
| } else if ($options['cat']) { | |
| if (!is_numeric($options['cat'])) { | |
| echo "Category ID needs to be a number.\n"; | |
| exit(1); | |
| } | |
| if (!is_numeric($options['start'])) { | |
| echo "Limit start needs to be a number.\n"; | |
| exit(1); | |
| } | |
| if (!is_numeric($options['limit'])) { | |
| echo "Limit count needs to be a number.\n"; | |
| exit(1); | |
| } | |
| $courseids = $DB->get_records('course', array('category' => $options['cat']), 'sortorder ASC', 'id', $options['start'], $options['limit']); | |
| } | |
| if (!$courseids) { | |
| echo "No courses found.\n"; | |
| exit(1); | |
| } | |
| $lf = \core\lock\lock_config::get_lock_factory('local_elis_course_delete'); | |
| $num = 1; | |
| $count = count($courseids); | |
| foreach ($courseids as $courseid => $unused) { | |
| echo "Deleting $num of $count, course id ".$courseid.". "; | |
| $key = 'course:'.$courseid; | |
| $lock = $lf->get_lock($key, 0); | |
| if ($lock === false) { | |
| echo "Course lock in place, being deleted elsewhere."; | |
| } else { | |
| if ($course = $DB->get_record('course', array('id' => $courseid))) { | |
| if (delete_course($course, false)) { | |
| echo "Successful $course->shortname"; | |
| } else { | |
| echo "Error $course->shortname"; | |
| } | |
| } else { | |
| echo "Course was already deleted"; | |
| } | |
| $lock->release(); | |
| } | |
| $num++; | |
| print "\n"; | |
| } | |
| fix_course_sortorder(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment