admin_externalpage_setup('reportbackups');
echo $OUTPUT->header();
-/// Scheduled backups are disabled by the server admin
- if (!empty($CFG->disablescheduledbackups)) {
- print_error('scheduledbackupsdisabled', 'error');
- }
-
-/// Scheduled backups aren't active by the site admin
+/// Automated backups aren't active by the site admin
$backup_config = backup_get_config();
- if (empty($backup_config->backup_sche_active)) {
- echo $OUTPUT->notification(get_string('scheduledbackupsinactive'));
+ if (empty($backup_config->backup_auto_active)) {
+ echo $OUTPUT->notification(get_string('automatedbackupsinactive', 'backup'));
}
/// Get needed strings
$ADMIN->add('backups', $temp);
/// "backups" settingpage
- $temp = new admin_settingpage('scheduled', get_string('scheduledsettings','backup'), 'moodle/backup:backupcourse');
- $temp->add(new admin_setting_configcheckbox('backup/backup_sche_modules', get_string('includemodules'), get_string('backupincludemoduleshelp'), 0));
- $temp->add(new admin_setting_configcheckbox('backup/backup_sche_withuserdata', get_string('includemoduleuserdata'), get_string('backupincludemoduleuserdatahelp'), 0));
- $temp->add(new admin_setting_configselect('backup/backup_sche_users', get_string('users'), get_string('backupusershelp'),
- 0, array(0 => get_string('all'), 1 => get_string('course'))));
- $temp->add(new admin_setting_configcheckbox('backup/backup_sche_logs', get_string('logs'), get_string('backuplogshelp'), 0));
- $temp->add(new admin_setting_configcheckbox('backup/backup_sche_userfiles', get_string('userfiles'), get_string('backupuserfileshelp'), 0));
- $temp->add(new admin_setting_configcheckbox('backup/backup_sche_coursefiles', get_string('coursefiles'), get_string('backupcoursefileshelp'), 0));
- $temp->add(new admin_setting_configcheckbox('backup/backup_sche_sitefiles', get_string('sitefiles'), get_string('backupsitefileshelp'), 0));
- $temp->add(new admin_setting_configcheckbox('backup_sche_gradebook_history', get_string('gradebookhistories', 'grades'), get_string('backupgradebookhistoryhelp'), 0));
- $temp->add(new admin_setting_configcheckbox('backup/backup_sche_messages', get_string('messages', 'message'), get_string('backupmessageshelp','message'), 0));
- $temp->add(new admin_setting_configcheckbox('backup/backup_sche_blogs', get_string('blogs', 'blog'), get_string('backupblogshelp','blog'), 0));
-
+ $temp = new admin_settingpage('automated', get_string('automatedsetup','backup'), 'moodle/backup:backupcourse');
+ $temp->add(new admin_setting_configcheckbox('backup/backup_auto_active', get_string('active'), get_string('backupactivehelp'), 0));
+ $temp->add(new admin_setting_special_backupdays());
+ $temp->add(new admin_setting_configtime('backup/backup_auto_hour', 'backup_auto_minute', get_string('executeat'),
+ get_string('backupexecuteathelp'), array('h' => 0, 'm' => 0)));
+ $storageoptions = array(
+ 0 => get_string('storagecourseonly', 'backup'),
+ 1 => get_string('storageexternalonly', 'backup'),
+ 2 => get_string('storagecourseandexternal', 'backup')
+ );
+ $temp->add(new admin_setting_configselect('backup/backup_auto_storage', get_string('automatedstorage', 'backup'), get_string('automatedstoragehelp', 'backup'), 0, $storageoptions));
+ $temp->add(new admin_setting_configdirectory('backup/backup_auto_destination', get_string('saveto'), get_string('backupsavetohelp'), ''));
$keepoptoins = array(
0 => get_string('all'), 1 => '1',
2 => '2',
300 => '300',
400 => '400',
500 => '500');
- $temp->add(new admin_setting_configselect('backup/backup_sche_keep', get_string('keep'),
- get_string('backupkeephelp'), 1, $keepoptoins));
- $temp->add(new admin_setting_configcheckbox('backup/backup_sche_active', get_string('active'), get_string('backupactivehelp'), 0));
- $temp->add(new admin_setting_special_backupdays());
- $temp->add(new admin_setting_configtime('backup/backup_sche_hour', 'backup_sche_minute', get_string('executeat'),
- get_string('backupexecuteathelp'), array('h' => 0, 'm' => 0)));
- $temp->add(new admin_setting_configdirectory('backup/backup_sche_destination', get_string('saveto'), get_string('backupsavetohelp'), ''));
+ $temp->add(new admin_setting_configselect('backup/backup_auto_keep', get_string('keep'), get_string('backupkeephelp'), 1, $keepoptoins));
+
+
+ $temp->add(new admin_setting_heading('automatedsettings', get_string('automatedsettings','backup'), ''));
+ $temp->add(new admin_setting_configcheckbox('backup/backup_auto_users', get_string('users'), get_string('backupusershelp'), 1));
+ $temp->add(new admin_setting_configcheckbox('backup/backup_auto_role_assignments', get_string('generalroleassignments','backup'), get_string('configgeneralroleassignments','backup'), 1));
+ $temp->add(new admin_setting_configcheckbox('backup/backup_auto_user_files', get_string('generaluserfiles', 'backup'), get_string('configgeneraluserfiles','backup'), 1));
+ $temp->add(new admin_setting_configcheckbox('backup/backup_auto_activities', get_string('generalactivities','backup'), get_string('configgeneralactivities','backup'), 1));
+ $temp->add(new admin_setting_configcheckbox('backup/backup_auto_blocks', get_string('generalblocks','backup'), get_string('configgeneralblocks','backup'), 1));
+ $temp->add(new admin_setting_configcheckbox('backup/backup_auto_filters', get_string('generalfilters','backup'), get_string('configgeneralfilters','backup'), 1));
+ $temp->add(new admin_setting_configcheckbox('backup/backup_auto_comments', get_string('generalcomments','backup'), get_string('configgeneralcomments','backup'), 1));
+ $temp->add(new admin_setting_configcheckbox('backup/backup_auto_userscompletion', get_string('generaluserscompletion','backup'), get_string('configgeneraluserscompletion','backup'), 1));
+ $temp->add(new admin_setting_configcheckbox('backup/backup_auto_logs', get_string('logs'), get_string('backuplogshelp'), 0));
+ $temp->add(new admin_setting_configcheckbox('backup/backup_auto_histories', get_string('generalhistories','backup'), get_string('configgeneralhistories','backup'), 0));
+
+
+ //$temp->add(new admin_setting_configcheckbox('backup/backup_auto_messages', get_string('messages', 'message'), get_string('backupmessageshelp','message'), 0));
+ //$temp->add(new admin_setting_configcheckbox('backup/backup_auto_blogs', get_string('blogs', 'blog'), get_string('backupblogshelp','blog'), 0));
$ADMIN->add('backups', $temp);
const INTERACTIVE_NO = false;
// Predefined modes (purposes) of the backup
- const MODE_GENERAL = 10;
- const MODE_IMPORT = 20;
- const MODE_HUB = 30;
- const MODE_SAMESITE = 40;
+ const MODE_GENERAL = 10;
+ const MODE_IMPORT = 20;
+ const MODE_HUB = 30;
+ const MODE_SAMESITE = 40;
+ const MODE_AUTOMATED = 50;
// Target (new/existing/current/adding/deleting)
const TARGET_CURRENT_DELETING = 0;
* @param int $id The ID of the item to backup; e.g the course id
* @param int $format The backup format to use; Most likely backup::FORMAT_MOODLE
* @param bool $interactive Whether this backup will require user interaction; backup::INTERACTIVE_YES or INTERACTIVE_NO
- * @param int $mode One of backup::MODE_GENERAL, MODE_IMPORT, MODE_SAMESITE, MODE_HUB
+ * @param int $mode One of backup::MODE_GENERAL, MODE_IMPORT, MODE_SAMESITE, MODE_HUB, MODE_AUTOMATED
* @param int $userid The id of the user making the backup
*/
public function __construct($type, $id, $format, $interactive, $mode, $userid){
echo $renderer->backup_files_viewer($treeview_options);
echo $OUTPUT->container_end();
+$automatedbackups = get_config('backup', 'backup_auto_active');
+if (!empty($automatedbackups)) {
+ echo $OUTPUT->heading_with_help(get_string('choosefilefromautomatedbackup', 'backup'), 'choosefilefromautomatedbackup', 'backup');
+ echo $OUTPUT->container_start();
+ $treeview_options = array();
+ $user_context = get_context_instance(CONTEXT_USER, $USER->id);
+ $treeview_options['filecontext'] = $context;
+ $treeview_options['currentcontext'] = $context;
+ $treeview_options['component'] = 'backup';
+ $treeview_options['context'] = $context;
+ $treeview_options['filearea'] = 'automated';
+ $renderer = $PAGE->get_renderer('core', 'backup');
+ echo $renderer->backup_files_viewer($treeview_options);
+ echo $OUTPUT->container_end();
+}
+
echo $OUTPUT->footer();
--- /dev/null
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+
+/**
+ * Utility helper for automated backups run through cron.
+ *
+ * @package core
+ * @subpackage backup
+ * @copyright 2010 Sam Hemelryk
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * This class is an abstract class with methods that can be called to aid the
+ * running of automated backups over cron.
+ */
+abstract class backup_cron_automated_helper {
+
+ /** automated backups are active and ready to run */
+ const STATE_OK = 0;
+ /** automated backups are disabled and will not be run */
+ const STATE_DISABLED = 1;
+ /** automated backups are all ready running! */
+ const STATE_RUNNING = 2;
+
+ /** Course automated backup completed successfully */
+ const BACKUP_STATUS_OK = 1;
+ /** Course automated backup errored */
+ const BACKUP_STATUS_ERROR = 0;
+ /** Course automated backup never finished */
+ const BACKUP_STATUS_UNFINISHED = 2;
+ /** Course automated backup was skipped */
+ const BACKUP_STATUS_SKIPPED = 3;
+
+
+ /**
+ * Runs the automated backups if required
+ *
+ * @global moodle_database $DB
+ */
+ public static function run_automated_backup() {
+ global $CFG, $DB;
+
+ $status = true;
+ $emailpending = false;
+ $now = time();
+
+ mtrace("Checking automated backup status",'...');
+ $state = backup_cron_automated_helper::get_automated_backup_state();
+ if ($state === backup_cron_automated_helper::STATE_DISABLED) {
+ mtrace('INACTIVE');
+ return true;
+ } else if ($state === backup_cron_automated_helper::STATE_RUNNING) {
+ mtrace('RUNNING');
+ mtrace("automated backup seems to be running. Execution delayed");
+ return true;
+ } else {
+ mtrace('OK');
+ }
+ backup_cron_automated_helper::set_state_running();
+
+ mtrace("Getting admin info");
+ $admin = get_admin();
+ if (!$admin) {
+ mtrace("Error: No admin account was found");
+ $state = false;
+ }
+
+ if ($status) {
+ mtrace("Checking courses");
+ mtrace("Skipping deleted courses", '...');
+ mtrace(sprintf("%d courses", backup_cron_automated_helper::remove_deleted_courses_from_schedule()));
+ }
+
+ if ($status) {
+
+ mtrace('Running required automated backups...');
+
+ // This could take a while!
+ @set_time_limit(0);
+ raise_memory_limit(MEMORY_EXTRA);
+
+ $nextstarttime = backup_cron_automated_helper::calculate_next_automated_backup($admin->timezone, $now);
+ $showtime = "undefined";
+ if ($nextstarttime > 0) {
+ $showtime = userdate($nextstarttime,"",$admin->timezone);
+ }
+
+ $rs = $DB->get_recordset('course');
+ foreach ($rs as $course) {
+ $backupcourse = $DB->get_record('backup_courses', array('courseid'=>$course->id));
+ if (!$backupcourse) {
+ $backupcourse = new stdClass;
+ $backupcourse->courseid = $course->id;
+ $DB->insert_record('backup_courses',$backupcourse);
+ $backupcourse = $DB->get_record('backup_courses', array('courseid'=>$course->id));
+ }
+
+ // Skip backup of unavailable courses that have remained unmodified in a month
+ $skipped = false;
+ if (empty($course->visible) && ($now - $course->timemodified) > 31*24*60*60) { //Hidden + unmodified last month
+ $backupcourse->laststatus = backup_cron_automated_helper::BACKUP_STATUS_SKIPPED;
+ $DB->update_record('backup_courses', $backupcourse);
+ $skipped = true;
+ } else if ($backupcourse->nextstarttime > 0 && $backupcourse->nextstarttime < $now) {
+ mtrace('Backing up '.$course->fullname, '...');
+
+ //We have to send a email because we have included at least one backup
+ $emailpending = true;
+
+ //Only make the backup if laststatus isn't 2-UNFINISHED (uncontrolled error)
+ if ($backupcourse->laststatus != 2) {
+ //Set laststarttime
+ $starttime = time();
+
+ $backupcourse->laststarttime = time();
+ $backupcourse->laststatus = backup_cron_automated_helper::BACKUP_STATUS_UNFINISHED;
+ $DB->update_record('backup_courses', $backupcourse);
+
+ $backupcourse->laststatus = backup_cron_automated_helper::launch_automated_backup($course, $backupcourse->laststarttime, $admin->id);
+ $backupcourse->lastendtime = time();
+ $backupcourse->nextstarttime = $nextstarttime;
+
+ $DB->update_record('backup_courses', $backupcourse);
+
+ if ($backupcourse->laststatus) {
+ // Clean up any excess course backups now that we have
+ // taken a successful backup.
+ $removedcount = backup_cron_automated_helper::remove_excess_backups($course);
+ }
+ }
+
+ mtrace("complete - next execution: $showtime");
+ }
+ }
+ $rs->close();
+ }
+
+ //Send email to admin if necessary
+ if ($emailpending) {
+ mtrace("Sending email to admin");
+ $message = "";
+
+ $count = backup_cron_automated_helper::get_backup_status_array();
+ $haserrors = ($count[backup_cron_automated_helper::BACKUP_STATUS_ERROR] != 0 || $count[backup_cron_automated_helper::BACKUP_STATUS_UNFINISHED] != 0);
+
+ //Build the message text
+ //Summary
+ $message .= get_string('summary')."\n";
+ $message .= "==================================================\n";
+ $message .= " ".get_string('courses').": ".array_sum($count)."\n";
+ $message .= " ".get_string('ok').": ".$count[backup_cron_automated_helper::BACKUP_STATUS_OK]."\n";
+ $message .= " ".get_string('skipped').": ".$count[backup_cron_automated_helper::BACKUP_STATUS_SKIPPED]."\n";
+ $message .= " ".get_string('error').": ".$count[backup_cron_automated_helper::BACKUP_STATUS_ERROR]."\n";
+ $message .= " ".get_string('unfinished').": ".$count[backup_cron_automated_helper::BACKUP_STATUS_UNFINISHED]."\n\n";
+
+ //Reference
+ if ($haserrors) {
+ $message .= " ".get_string('backupfailed')."\n\n";
+ $dest_url = "$CFG->wwwroot/$CFG->admin/report/backups/index.php";
+ $message .= " ".get_string('backuptakealook','',$dest_url)."\n\n";
+ //Set message priority
+ $admin->priority = 1;
+ //Reset unfinished to error
+ $DB->set_field('backup_courses','laststatus','0', array('laststatus'=>'2'));
+ } else {
+ $message .= " ".get_string('backupfinished')."\n";
+ }
+
+ //Build the message subject
+ $site = get_site();
+ $prefix = $site->shortname.": ";
+ if ($haserrors) {
+ $prefix .= "[".strtoupper(get_string('error'))."] ";
+ }
+ $subject = $prefix.get_string('automatedbackupstatus', 'backup');
+
+ //Send the message
+ $eventdata = new stdClass();
+ $eventdata->modulename = 'moodle';
+ $eventdata->userfrom = $admin;
+ $eventdata->userto = $admin;
+ $eventdata->subject = $subject;
+ $eventdata->fullmessage = $message;
+ $eventdata->fullmessageformat = FORMAT_PLAIN;
+ $eventdata->fullmessagehtml = '';
+ $eventdata->smallmessage = '';
+
+ $eventdata->component = 'moodle';
+ $eventdata->name = 'backup';
+
+ message_send($eventdata);
+ }
+
+ //Everything is finished stop backup_auto_running
+ backup_cron_automated_helper::set_state_running(false);
+
+ mtrace('Automated backups complete.');
+
+ return $status;
+ }
+
+ /**
+ * Gets the results from the last automated backup that was run based upon
+ * the statuses of the courses that were looked at.
+ *
+ * @global moodle_database $DB
+ * @return array
+ */
+ public static function get_backup_status_array() {
+ global $DB;
+
+ $result = array(
+ self::BACKUP_STATUS_ERROR => 0,
+ self::BACKUP_STATUS_OK => 0,
+ self::BACKUP_STATUS_UNFINISHED => 0,
+ self::BACKUP_STATUS_SKIPPED => 0,
+ );
+
+ $statuses = $DB->get_records_sql('SELECT DISTINCT bc.laststatus, COUNT(bc.courseid) statuscount FROM {backup_courses} bc GROUP BY bc.laststatus');
+
+ foreach ($statuses as $status) {
+ if (empty($status->statuscount)) {
+ $status->statuscount = 0;
+ }
+ $result[(int)$status->laststatus] += $status->statuscount;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Works out the next time the automated backup should be run.
+ *
+ * @param mixed $timezone
+ * @param int $now
+ * @return int
+ */
+ public static function calculate_next_automated_backup($timezone, $now) {
+
+ $result = -1;
+ $config = get_config('backup');
+ $midnight = usergetmidnight($now, $timezone);
+ $date = usergetdate($now, $timezone);
+
+ //Get number of days (from today) to execute backups
+ $automateddays = substr($config->backup_auto_weekdays,$date['wday']) . $config->backup_auto_weekdays;
+ $daysfromtoday = strpos($automateddays, "1");
+ if (empty($daysfromtoday)) {
+ $daysfromtoday = 1;
+ }
+
+ //If some day has been found
+ if ($daysfromtoday !== false) {
+ //Calculate distance
+ $dist = ($daysfromtoday * 86400) + //Days distance
+ ($config->backup_auto_hour * 3600) + //Hours distance
+ ($config->backup_auto_minute * 60); //Minutes distance
+ $result = $midnight + $dist;
+ }
+
+ //If that time is past, call the function recursively to obtain the next valid day
+ if ($result > 0 && $result < time()) {
+ $result = self::calculate_next_automated_backup($timezone, $result);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Launches a automated backup routine for the given course
+ *
+ * @param stdClass $course
+ * @param int $starttime
+ * @param int $userid
+ * @return bool
+ */
+ public static function launch_automated_backup($course, $starttime, $userid) {
+
+ $config = get_config('backup');
+ $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_AUTOMATED, $userid);
+
+ try {
+
+ $settings = array(
+ 'users' => 'backup_auto_users',
+ 'role_assignments' => 'backup_auto_users',
+ 'user_files' => 'backup_auto_user_files',
+ 'activities' => 'backup_auto_activities',
+ 'blocks' => 'backup_auto_blocks',
+ 'filters' => 'backup_auto_filters',
+ 'comments' => 'backup_auto_comments',
+ 'completion_information' => 'backup_auto_userscompletion',
+ 'logs' => 'backup_auto_logs',
+ 'histories' => 'backup_auto_histories'
+ );
+ foreach ($settings as $setting => $configsetting) {
+ if ($bc->get_plan()->setting_exists($setting)) {
+ $bc->get_plan()->get_setting($setting)->set_value($config->{$configsetting});
+ }
+ }
+
+ // Set the default filename
+ $format = $bc->get_format();
+ $type = $bc->get_type();
+ $id = $bc->get_id();
+ $users = $bc->get_plan()->get_setting('users')->get_value();
+ $anonymised = $bc->get_plan()->get_setting('anonymize')->get_value();
+ $bc->get_plan()->get_setting('filename')->set_value(backup_plan_dbops::get_default_backup_filename($format, $type, $id, $users, $anonymised));
+
+ $bc->set_status(backup::STATUS_AWAITING);
+
+ $outcome = $bc->execute_plan();
+ $results = $bc->get_results();
+ $file = $results['backup_destination'];
+ $dir = $config->backup_auto_destination;
+ $storage = (int)$config->backup_auto_storage;
+ if (!file_exists($dir) || !is_dir($dir) || !is_writable($dir)) {
+ $dir = null;
+ }
+ if (!empty($dir) && $storage !== 0) {
+ $filename = self::get_external_filename($course->id, $format, $type, $users, $anonymised);
+ $outcome = $file->copy_content_to($dir.'/'.$filename);
+ if ($outcome && $storage === 1) {
+ $file->delete();
+ }
+ }
+
+ } catch (backup_exception $e) {
+ $bc->log('backup_auto_failed_on_course', backup::LOG_WARNING, $course->shortname);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Gets the filename to use for the backup when it is being moved to an
+ * external location.
+ *
+ * Note: we use the course id in the filename rather than the course shortname
+ * because it may contain UTF-8 characters that could cause problems for the
+ * recieving filesystem.
+ *
+ * @param int $courseid
+ * @param string $format One of backup::FORMAT_
+ * @param string $type One of backup::TYPE_
+ * @param bool $users Should be true is users were included in the backup
+ * @param bool $anonymised Should be true is user information was anonymized.
+ * @return string The filename to use
+ */
+ public static function get_external_filename($courseid, $format, $type, $users, $anonymised) {
+ $backupword = str_replace(' ', '_', moodle_strtolower(get_string('backupfilename')));
+ $backupword = trim(clean_filename($backupword), '_');
+ // Calculate date
+ $backupdateformat = str_replace(' ', '_', get_string('backupnameformat', 'langconfig'));
+ $date = userdate(time(), $backupdateformat, 99, false);
+ $date = moodle_strtolower(trim(clean_filename($date), '_'));
+ // Calculate info
+ $info = '';
+ if (!$users) {
+ $info = 'nu';
+ } else if ($anonymised) {
+ $info = 'an';
+ }
+ return $backupword.'-'.$format.'-'.$type.'-'.$courseid.'-'.$date.'-'.$info.'.mbz';
+ }
+
+ /**
+ * Removes deleted courses fromn the backup_courses table so that we don't
+ * waste time backing them up.
+ *
+ * @global moodle_database $DB
+ * @return int
+ */
+ public static function remove_deleted_courses_from_schedule() {
+ global $DB;
+ $skipped = 0;
+ $sql = "SELECT bc.courseid FROM {backup_courses} bc WHERE bc.courseid NOT IN (SELECT c.id FROM {course} c)";
+ $rs = $DB->get_recordset_sql($sql);
+ foreach ($rs as $deletedcourse) {
+ //Doesn't exist, so delete from backup tables
+ $DB->delete_records('backup_courses', array('courseid'=>$deletedcourse->courseid));
+ $skipped++;
+ }
+ $rs->close();
+ return $skipped;
+ }
+
+ /**
+ * Gets the state of the automated backup system.
+ *
+ * @global moodle_database $DB
+ * @return int One of self::STATE_*
+ */
+ public static function get_automated_backup_state() {
+ global $DB;
+
+ $config = get_config('backup');
+ if (empty($config->backup_auto_active)) {
+ return self::STATE_DISABLED;
+ } else if (!empty($config->backup_auto_running)) {
+ // TODO: We should find some way of checking whether the automated
+ // backup has infact finished. In 1.9 this was being done by checking
+ // the log entries.
+ return self::STATE_RUNNING;
+ }
+ return self::STATE_OK;
+ }
+
+ /**
+ * Sets the state of the automated backup system.
+ *
+ * @param bool $running
+ * @return bool
+ */
+ public static function set_state_running($running = true) {
+ if ($running === true) {
+ if (self::get_automated_backup_state() === self::STATE_RUNNING) {
+ throw new backup_exception('backup_automated_already_running');
+ }
+ set_config('backup_auto_running', '1', 'backup');
+ } else {
+ unset_config('backup_auto_running', 'backup');
+ }
+ return true;
+ }
+
+ /**
+ * 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
+ * @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;
+
+ $backupword = str_replace(' ', '_', moodle_strtolower(get_string('backupfilename')));
+ $backupword = trim(clean_filename($backupword), '_');
+
+ 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 = get_context_instance(CONTEXT_COURSE, $course->id);
+ $component = 'backup';
+ $filearea = 'automated';
+ $itemid = 0;
+ $files = array();
+ foreach ($fs->get_area_files($context->id, $component, $filearea, $itemid) as $file) {
+ if (strpos($file->get_filename(), $backupword) !== 0) {
+ continue;
+ }
+ $files[$file->get_timemodified()] = $file;
+ }
+ arsort($files);
+ $remove = array_splice($files, $keep);
+ foreach ($remove as $file) {
+ $file->delete();
+ }
+ //mtrace('Removed '.count($remove).' old backup file(s) from the data directory');
+ }
+
+ // Clean up excess backups in the specified external directory
+ if (!empty($dir) && ($storage == 1 || $storage == 2)) {
+ // Calculate backup filename regex
+
+ $filename = $backupword . '-' . backup::FORMAT_MOODLE . '-' . backup::TYPE_1COURSE . '-' .$course->id . '-';
+
+ $regex = '#^'.preg_quote($filename, '#').'(\d{8})\-(\d{4})\-[a-z]{2}\.mbz$#S';
+
+ $files = array();
+ foreach (scandir($dir) as $file) {
+ if (preg_match($regex, $file, $matches)) {
+ $files[$file] = $matches[1].$matches[2];
+ }
+ }
+ if (count($files) <= $keep) {
+ // There are less matching files than the desired number to keep
+ // do there is nothing to clean up.
+ return 0;
+ }
+ 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;
+ }
+}
\ No newline at end of file
break;
}
+ if ($backupmode == backup::MODE_AUTOMATED) {
+ // Automated backups have there own special area!
+ $filearea = 'automated';
+ }
+
// Backups of type HUB (by definition never have user info)
// are sent to user's "user_tohub" file area. The upload process
// will be responsible for cleaning that filearea once finished
$files = $viewer->files;
$table = new html_table();
+ $table->attributes['class'] = 'backup-files-table generaltable';
$table->head = array(get_string('filename', 'backup'), get_string('time'), get_string('size'), get_string('download'), get_string('restore'));
- $table->align = array('left', 'left', 'left', 'center', 'left', 'center');
$table->width = '100%';
$table->data = array();
// any existing key.
// $CFG->mnetkeylifetime = 28;
//
-// Prevent scheduled backups from operating (and hide the GUI for them)
-// Useful for webhost operators who have alternate methods of backups
-// $CFG->disablescheduledbackups = true;
-//
// Allow user passwords to be included in backup files. Very dangerous
// setting as far as it publishes password hashes that can be unencrypted
// if the backup file is publicy available. Use it only if you can guarantee
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+$string['automatedbackupschedule'] = 'Schedule';
+$string['automatedbackupschedulehelp'] = 'Choose which days of the week to perform automated backups.';
+$string['automatedbackupsinactive'] = 'Automated backups haven\'t been enabled by the site admin';
+$string['automatedbackupstatus'] = 'Automated backup status';
+
+
$string['backupactivity'] = 'Backup activity: {$a}';
$string['backupcourse'] = 'Backup course: {$a}';
$string['backupcoursedetails'] = 'Course details';
$string['choosefilefromuserbackup_help'] = 'When backup courses with "Anonymize user information" option ticked, backup files will be stored here';
$string['choosefilefromactivitybackup'] = 'Activity backup area';
$string['choosefilefromactivitybackup_help'] = 'When backup activities using default settings, backup files will be stored here';
+$string['choosefilefromautomatedbackup'] = 'Automated backups';
+$string['choosefilefromautomatedbackup_help'] = 'Contains automatically generated backups.';
$string['configgeneralactivities'] = 'Sets the default for including activities in a backup.';
$string['configgeneralanonymize'] = 'If enabled all information pertaining to users will be anonymised by default.';
$string['configgeneralblocks'] = 'Sets the default for including blocks in a backup.';
$string['rootsettinguserscompletion'] = 'Include user completion details';
$string['rootsettinglogs'] = 'Include course logs';
$string['rootsettinggradehistories'] = 'Include grade history';
-$string['scheduledsettings'] = 'Scheduled backup settings';
+$string['automatedsetup'] = 'Automated backup setup';
+$string['automatedsettings'] = 'Automated backup settings';
+$string['automatedstorage'] = 'Automated backup storage';
+$string['automatedstoragehelp'] = 'Choose the location where you want backups to be stored when they are automatically created.';
+$string['storagecourseonly'] = 'Course backup filearea';
+$string['storagecourseandexternal'] = 'Course backup filearea and the specified directory';
+$string['storageexternalonly'] = 'Specified directory for automated backups';
$string['sectionincanduser'] = 'Included in backup along with user information';
$string['sectioninc'] = 'Included in backup (no user information)';
$string['sectionactivities'] = 'Activities';
$string['backuporiginalname'] = 'Backup name';
$string['backuproleassignments'] = 'Backup role assignments for these roles';
$string['backupsavetohelp'] = 'Full path to the directory where you want to save the backup files<br />(leave blank to save in its course default dir)';
-$string['backupschedulehelp'] = 'Choose which days of the week to perform automated backups.';
$string['backupsitefileshelp'] = 'If enabled then site files used in courses will be included in automated backups';
$string['backuptakealook'] = 'Please take a look at your backup logs in:
{$a}';
$string['showtopicfromothers'] = 'Show topic';
$string['showweekfromothers'] = 'Show week';
$string['schedule'] = 'Schedule';
-$string['scheduledbackupsinactive'] = 'Scheduled backups haven\'t been enabled by the site admin';
-$string['scheduledbackupstatus'] = 'Scheduled backup status';
$string['since'] = 'Since';
$string['sincelast'] = 'since last login';
$string['site'] = 'Site';
$string['saveas'] = 'Save as';
$string['saved'] = 'Saved';
$string['saving'] = 'Saving';
+$string['automatedbackup'] = 'Automated backups';
$string['search'] = 'Search';
$string['searching'] = 'Search in';
$string['select'] = 'Select';
* Calls parent::__construct with specific arguments
*/
public function __construct() {
- parent::__construct('backup_sche_weekdays', get_string('schedule'), get_string('backupschedulehelp'), array(), NULL);
+ parent::__construct('backup_auto_weekdays', get_string('automatedbackupschedule','backup'), get_string('automatedbackupschedulehelp','backup'), array(), NULL);
$this->plugin = 'backup';
}
/**
} // End of occasional clean-up tasks
- // Disabled until implemented. MDL-21432, MDL-22184
- if (1 == 2 && empty($CFG->disablescheduledbackups)) { // Defined in config.php
- //Execute backup's cron
- //Perhaps a long time and memory could help in large sites
- @set_time_limit(0);
- raise_memory_limit(MEMORY_EXTRA);
- if (file_exists("$CFG->dirroot/backup/backup_scheduled.php") and
- file_exists("$CFG->dirroot/backup/backuplib.php") and
- file_exists("$CFG->dirroot/backup/lib.php") and
- file_exists("$CFG->libdir/blocklib.php")) {
- include_once("$CFG->dirroot/backup/backup_scheduled.php");
- include_once("$CFG->dirroot/backup/backuplib.php");
- include_once("$CFG->dirroot/backup/lib.php");
- mtrace("Running backups if required...");
-
- if (! schedule_backup_cron()) {
- mtrace("ERROR: Something went wrong while performing backup tasks!!!");
- } else {
- mtrace("Backup tasks finished.");
- }
- }
- }
+ // Run automated backups if required.
+ require_once($CFG->dirroot.'/backup/util/includes/backup_includes.php');
+ require_once($CFG->dirroot.'/backup/util/helper/backup_cron_helper.class.php');
+ backup_cron_automated_helper::run_automated_backup();
/// Run the auth cron, if any
/// before enrolments because it might add users that will be needed in enrol plugins
'clonepermissionsfrom' => 'moodle/restore:restorecourse'
),
+ 'moodle/restore:viewautomatedfilearea' => array(
+
+ 'riskbitmask' => RISK_SPAM | RISK_PERSONAL | RISK_XSS,
+
+ 'captype' => 'write',
+ 'contextlevel' => CONTEXT_COURSE,
+ ),
+
'moodle/restore:restoretargethub' => array(
'riskbitmask' => RISK_SPAM | RISK_PERSONAL | RISK_XSS,
'instantmessage' => array (
),
+ 'backup' => array (
+ 'capability' => 'moodle/site:config'
+ )
+
);
upgrade_main_savepoint(true, 2010110800);
}
+ if ($oldversion < 2010111000) {
+
+ // Clean up the old scheduled backup settings that are no longer relevant
+ update_fix_automated_backup_config();
+ upgrade_main_savepoint(true, 2010111000);
+ }
return true;
}
$DB->delete_records_select('role_names', 'contextid IN ('.$blockcontextidsstring.')');
$DB->delete_records_select('context', 'id IN ('.$blockcontextidsstring.')');
}
+
+/**
+ * This function is used to establish the automated backup settings using the
+ * original scheduled backup settings.
+ *
+ * @since 2010111000
+ */
+function update_fix_automated_backup_config() {
+ $mappings = array(
+ // Old setting => new setting
+ 'backup_sche_active' => 'backup_auto_active',
+ 'backup_sche_hour' => 'backup_auto_hour',
+ 'backup_sche_minute' => 'backup_auto_minute',
+ 'backup_sche_destination' => 'backup_auto_destination',
+ 'backup_sche_keep' => 'backup_auto_keep',
+ 'backup_sche_userfiles' => 'backup_auto_user_files',
+ 'backup_sche_modules' => 'backup_auto_activities',
+ 'backup_sche_logs' => 'backup_auto_logs',
+ 'backup_sche_messages' => 'backup_auto_messages',
+ 'backup_sche_blocks' => 'backup_auto_blocks',
+ 'backup_sche_weekdays' => 'backup_auto_weekdays',
+ 'backup_sche_users' => 'backup_auto_users',
+ 'backup_sche_blogs' => 'backup_auto_blogs',
+ 'backup_sche_coursefiles' => null,
+ 'backup_sche_sitefiles' => null,
+ 'backup_sche_withuserdata' => null,
+ 'backup_sche_metacourse' => null,
+ 'backup_sche_running' => null,
+ );
+
+ $oldconfig = get_config('backup');
+ foreach ($mappings as $oldsetting=>$newsetting) {
+ if (!isset($oldconfig->$oldsetting)) {
+ continue;
+ }
+ if ($newsetting !== null) {
+ $oldvalue = $oldconfig->$oldsetting;
+ set_config($newsetting, $oldvalue, 'backup');
+ }
+ unset_config($oldsetting, 'backup');
+ }
+
+ unset_config('backup_sche_gradebook_history');
+ unset_config('disablescheduleddbackups');
+}
\ No newline at end of file
return new file_info_stored($this->browser, $this->context, $storedfile, $urlbase, get_string('coursebackup', 'repository'), false, $downloadable, $uploadable, false);
}
+ /**
+ * Gets a stored file for the automated backup filearea directory
+ *
+ * @param int $itemid
+ * @param string $filepath
+ * @param string $filename
+ * @return file_info_context_course
+ */
+ protected function get_area_backup_automated($itemid, $filepath, $filename) {
+ global $CFG;
+
+ if (!has_capability('moodle/restore:viewautomatedfilearea', $this->context)) {
+ return null;
+ }
+ if (is_null($itemid)) {
+ return $this;
+ }
+
+ $fs = get_file_storage();
+
+ $filepath = is_null($filepath) ? '/' : $filepath;
+ $filename = is_null($filename) ? '.' : $filename;
+ if (!$storedfile = $fs->get_file($this->context->id, 'backup', 'automated', 0, $filepath, $filename)) {
+ if ($filepath === '/' and $filename === '.') {
+ $storedfile = new virtual_root_file($this->context->id, 'backup', 'automated', 0);
+ } else {
+ // not found
+ return null;
+ }
+ }
+
+ $downloadable = has_capability('moodle/site:config', $this->context);
+ $uploadable = false;
+
+ $urlbase = $CFG->wwwroot.'/pluginfile.php';
+ return new file_info_stored($this->browser, $this->context, $storedfile, $urlbase, get_string('automatedbackup', 'repository'), true, $downloadable, $uploadable, false);
+ }
+
protected function get_area_backup_section($itemid, $filepath, $filename) {
global $CFG, $DB;
if ($child = $this->get_area_backup_course(0, '/', '.')) {
$children[] = $child;
}
+ if ($child = $this->get_area_backup_automated(0, '/', '.')) {
+ $children[] = $child;
+ }
if ($child = $this->get_area_course_legacy(0, '/', '.')) {
$children[] = $child;
}
session_get_instance()->write_close();
send_stored_file($file, 60*60, 0, $forcedownload);
+ } else if ($filearea === 'automated' and $context->contextlevel == CONTEXT_COURSE) {
+ // Backup files that were generated by the automated backup systems.
+
+ require_login($course);
+ require_capability('moodle/site:config', $context);
+
+ $filename = array_pop($args);
+ $filepath = $args ? '/'.implode('/', $args).'/' : '/';
+ if (!$file = $fs->get_file($context->id, 'backup', 'automated', 0, $filepath, $filename) or $file->is_directory()) {
+ send_file_not_found();
+ }
+
+ session_get_instance()->write_close(); // unlock session during fileserving
+ send_stored_file($file, 0, 0, $forcedownload);
+
} else {
send_file_not_found();
}
.path-backup .mform .grouped_settings.activity_level .include_setting label {font-weight:normal;}
.path-backup .backup_progress {margin:10px;}
.path-backup .backup_progress .backup_stage {margin:5px 20px;}
+.backup-files-table .c0 {min-width:300px;}
+.backup-files-table .c1 {width:300px;}
+.backup-files-table .c2 {width:80px;}
+.backup-files-table .c3 {width:80px;}
+.backup-files-table .c4 {width:80px;}
/**
* Site registration
defined('MOODLE_INTERNAL') || die();
-$version = 2010110800; // YYYYMMDD = date of the last version bump
+$version = 2010111000; // YYYYMMDD = date of the last version bump
// XX = daily increments
$release = '2.0 RC1 (Build: 20101110)'; // Human-friendly version name