backup MDL-22184 Scheduled backups are now possible again through cron.
authorSam Hemelryk <sam@moodle.com>
Wed, 10 Nov 2010 06:07:43 +0000 (06:07 +0000)
committerSam Hemelryk <sam@moodle.com>
Wed, 10 Nov 2010 06:07:43 +0000 (06:07 +0000)
AMOS BEGIN
 MOV [move scheduledsetup,core_backup],[automatedsetup,core_backup]
 MOV [scheduledsettings,core_backup],[automatedsettings,core_backup]
 MOV [scheduledstorage,core_backup],[automatedstorage,core_backup]
 MOV [scheduledstoragehelp,core_backup],[automatedstoragehelp,core_backup]
 MOV [scheduledbackupsinactive,core],[automatedbackupsinactive,core_backup]
 MOV [scheduledbackupstatus,core],[automatedbackupstatus,core_backup]
 CPY [schedule,core],[automatedbackupschedule,core_backup]
 MOV [backupschedulehelp,core],[automatedbackupschedulehelp,core_backup]
AMOS END

22 files changed:
admin/report/backups/index.php
admin/settings/courses.php
backup/backup.class.php
backup/controller/backup_controller.class.php
backup/restorefile.php
backup/util/helper/backup_cron_helper.class.php [new file with mode: 0644]
backup/util/helper/backup_helper.class.php
backup/util/ui/renderer.php
config-dist.php
lang/en/backup.php
lang/en/moodle.php
lang/en/repository.php
lib/adminlib.php
lib/cronlib.php
lib/db/access.php
lib/db/messages.php
lib/db/upgrade.php
lib/db/upgradelib.php
lib/filebrowser/file_info_context_course.php
pluginfile.php
theme/standard/style/core.css
version.php

index 104d3d8..425206d 100644 (file)
     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
index 0e2a670..d8c7278 100644 (file)
@@ -108,19 +108,18 @@ if ($hassiteconfig
     $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',
@@ -135,13 +134,24 @@ if ($hassiteconfig
         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);
 
index 75efd31..b09d8e4 100644 (file)
@@ -49,10 +49,11 @@ abstract class backup implements checksumable {
     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;
index 8699fbc..6cadb7d 100644 (file)
@@ -72,7 +72,7 @@ class backup_controller extends backup implements loggable {
      * @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){
index 1ccd6cd..79f5f85 100755 (executable)
@@ -152,4 +152,20 @@ $renderer = $PAGE->get_renderer('core', 'backup');
 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();
diff --git a/backup/util/helper/backup_cron_helper.class.php b/backup/util/helper/backup_cron_helper.class.php
new file mode 100644 (file)
index 0000000..03d509d
--- /dev/null
@@ -0,0 +1,516 @@
+<?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
index 457adb0..d1fd91d 100644 (file)
@@ -229,6 +229,11 @@ abstract class backup_helper {
                 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
index 413e2ca..da2acaf 100644 (file)
@@ -406,8 +406,8 @@ class core_backup_renderer extends plugin_renderer_base {
         $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();
 
index e2b8722..5693ef6 100644 (file)
@@ -155,10 +155,6 @@ $CFG->admin = 'admin';
 // 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
index 14115e1..deb34d3 100644 (file)
  * @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';
@@ -54,6 +60,8 @@ $string['choosefilefromuserbackup'] = 'User private backup area';
 $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.';
@@ -187,7 +195,13 @@ $string['rootsettingcomments'] = 'Include comments';
 $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';
index bf1d7ba..cc43180 100644 (file)
@@ -185,7 +185,6 @@ $string['backupnonisowarning'] = 'Warning: this backup is from a non-Unicode ver
 $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}';
@@ -1508,8 +1507,6 @@ $string['showthishelpinlanguage'] = 'Show this help in language: {$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';
index 526c095..8a986ad 100644 (file)
@@ -153,6 +153,7 @@ $string['save'] = 'Save';
 $string['saveas'] = 'Save as';
 $string['saved'] = 'Saved';
 $string['saving'] = 'Saving';
+$string['automatedbackup'] = 'Automated backups';
 $string['search'] = 'Search';
 $string['searching'] = 'Search in';
 $string['select'] = 'Select';
index 6c3d2e7..d44f3cb 100644 (file)
@@ -3514,7 +3514,7 @@ class admin_setting_special_backupdays extends admin_setting_configmulticheckbox
  * 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';
     }
     /**
index e70c18f..5dc3fd6 100644 (file)
@@ -333,28 +333,10 @@ function cron_run() {
 
     } // 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
index f9e60ea..b2a045e 100644 (file)
@@ -257,6 +257,14 @@ $capabilities = array(
         '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,
index e38ccd0..5873874 100644 (file)
@@ -41,4 +41,8 @@ $messageproviders = array (
     'instantmessage' => array (
     ),
 
+    'backup' => array (
+        'capability'  => 'moodle/site:config'
+    )
+
 );
index 6f3dfb2..670dbac 100644 (file)
@@ -5414,6 +5414,12 @@ WHERE gradeitemid IS NOT NULL AND grademax IS NOT NULL");
         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;
 }
index 74d0ba3..b3eeb67 100644 (file)
@@ -598,3 +598,48 @@ function upgrade_cleanup_unwanted_block_contexts($contextidarray) {
     $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
index 0263a8b..df8b0c4 100644 (file)
@@ -189,6 +189,44 @@ class file_info_context_course extends file_info {
         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;
 
@@ -264,6 +302,9 @@ class file_info_context_course extends file_info {
         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;
         }
index 9cb971b..0ab8428 100644 (file)
@@ -633,6 +633,21 @@ if ($component === 'blog') {
         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();
     }
index 7bef837..c3f8144 100644 (file)
@@ -359,6 +359,11 @@ table#tag-management-list {margin: 10px auto;width: 80%;}
 .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
index 024f3e9..aefc545 100644 (file)
@@ -29,7 +29,7 @@
 
 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