# This patch file was generated by NetBeans IDE
# This patch can be applied using context Tools: Apply Diff Patch action on respective folder.
# It uses platform neutral UTF-8 encoding.
# Above lines and this line are ignored by the patching process.
Index: moodle/admin/settings/courses.php
--- moodle/admin/settings/courses.php Base (1.49)
+++ moodle/admin/settings/courses.php Locally Modified (Based On 1.49)
@@ -108,19 +108,18 @@
     $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('scheduled', get_string('scheduledsetup','backup'), 'moodle/backup:backupcourse');
+    $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)));
+    $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_sche_storage', get_string('scheduledstorage', 'backup'), get_string('scheduledstoragehelp', 'backup'), 0, $storageoptions));
+    $temp->add(new admin_setting_configdirectory('backup/backup_sche_destination', get_string('saveto'), get_string('backupsavetohelp'), ''));
     $keepoptoins = array(
         0 => get_string('all'), 1 => '1',
         2 => '2',
@@ -137,12 +136,24 @@
         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_heading('scheduledsettings', get_string('scheduledsettings','backup'), ''));
+    $temp->add(new admin_setting_configcheckbox('backup/backup_sche_users', get_string('users'), get_string('backupusershelp'), 1));
+    $temp->add(new admin_setting_configcheckbox('backup/backup_sche_role_assignments', get_string('generalroleassignments','backup'), get_string('configgeneralroleassignments','backup'), 1));
+    $temp->add(new admin_setting_configcheckbox('backup/backup_sche_user_files', get_string('generaluserfiles', 'backup'), get_string('configgeneraluserfiles','backup'), 1));
+    $temp->add(new admin_setting_configcheckbox('backup/backup_sche_activities', get_string('generalactivities','backup'), get_string('configgeneralactivities','backup'), 1));
+    $temp->add(new admin_setting_configcheckbox('backup/backup_sche_blocks', get_string('generalblocks','backup'), get_string('configgeneralblocks','backup'), 1));
+    $temp->add(new admin_setting_configcheckbox('backup/backup_sche_filters', get_string('generalfilters','backup'), get_string('configgeneralfilters','backup'), 1));
+    $temp->add(new admin_setting_configcheckbox('backup/backup_sche_comments', get_string('generalcomments','backup'), get_string('configgeneralcomments','backup'), 1));
+    $temp->add(new admin_setting_configcheckbox('backup/backup_sche_userscompletion', get_string('generaluserscompletion','backup'), get_string('configgeneraluserscompletion','backup'), 1));
+    $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_histories', get_string('generalhistories','backup'), get_string('configgeneralhistories','backup'), 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));
+
     $ADMIN->add('backups', $temp);
 
 } // end of speedup
Index: moodle/backup/backup.class.php
--- moodle/backup/backup.class.php Base (1.11)
+++ moodle/backup/backup.class.php Locally Modified (Based On 1.11)
@@ -49,10 +49,11 @@
     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_SCHEDULED = 50;
 
     // Target (new/existing/current/adding/deleting)
     const TARGET_CURRENT_DELETING = 0;
Index: moodle/backup/restorefile.php
--- moodle/backup/restorefile.php Base (1.5)
+++ moodle/backup/restorefile.php Locally Modified (Based On 1.5)
@@ -152,4 +152,20 @@
 echo $renderer->backup_files_viewer($treeview_options);
 echo $OUTPUT->container_end();
 
+$scheduledbackups = get_config('backup', 'backup_sche_active');
+if (!empty($scheduledbackups)) {
+    echo $OUTPUT->heading_with_help(get_string('choosefilefromscheduledbackup', 'backup'), 'choosefilefromscheduledbackup', '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']    = 'scheduled';
+    $renderer = $PAGE->get_renderer('core', 'backup');
+    echo $renderer->backup_files_viewer($treeview_options);
+    echo $OUTPUT->container_end();
+}
+
 echo $OUTPUT->footer();
Index: moodle/backup/util/helper/backup_cron_helper.class.php
--- moodle/backup/util/helper/backup_cron_helper.class.php No Base Revision
+++ moodle/backup/util/helper/backup_cron_helper.class.php Locally New
@@ -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 scheduled 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 scheduled backups over cron.
+ */
+abstract class backup_cron_scheduled_helper {
+
+    /** Scheduled backups are active and ready to run */
+    const STATE_OK = 0;
+    /** Scheduled backups are disabled and will not be run */
+    const STATE_DISABLED = 1;
+    /** Scheduled backups are all ready running! */
+    const STATE_RUNNING = 2;
+
+    /** Course scheduled backup completed successfully */
+    const BACKUP_STATUS_OK = 1;
+    /** Course scheduled backup errored */
+    const BACKUP_STATUS_ERROR = 0;
+    /** Course scheduled backup never finished */
+    const BACKUP_STATUS_UNFINISHED = 2;
+    /** Course scheduled backup was skipped */
+    const BACKUP_STATUS_SKIPPED = 3;
+
+
+    /**
+     * Runs the scheduled backups if required
+     *
+     * @global moodle_database $DB
+     */
+    public static function run_scheduled_backup() {
+        global $CFG, $DB;
+
+        $status = true;
+        $emailpending = false;
+        $now = time();
+
+        mtrace("Checking scheduled backup status",'...');
+        $state = backup_cron_scheduled_helper::get_scheduled_backup_state();
+        if ($state === backup_cron_scheduled_helper::STATE_DISABLED) {
+            mtrace('INACTIVE');
+            return true;
+        } else if ($state === backup_cron_scheduled_helper::STATE_RUNNING) {
+            mtrace('RUNNING');
+            mtrace("Scheduled backup seems to be running. Execution delayed");
+            return true;
+        } else {
+            mtrace('OK');
+        }
+        backup_cron_scheduled_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_scheduled_helper::remove_deleted_courses_from_schedule()));
+        }
+
+        if ($status) {
+
+            mtrace('Running required scheduled backups...');
+
+            // This could take a while!
+            @set_time_limit(0);
+            raise_memory_limit(MEMORY_EXTRA);
+
+            $nextstarttime = backup_cron_scheduled_helper::calculate_next_scheduled_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_scheduled_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_scheduled_helper::BACKUP_STATUS_UNFINISHED;
+                        $DB->update_record('backup_courses', $backupcourse);
+
+                        $backupcourse->laststatus = backup_cron_scheduled_helper::launch_scheduled_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_scheduled_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_scheduled_helper::get_backup_status_array();
+            $haserrors = ($count[backup_cron_scheduled_helper::BACKUP_STATUS_ERROR] != 0 || $count[backup_cron_scheduled_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_scheduled_helper::BACKUP_STATUS_OK]."\n";
+            $message .= "  ".get_string('skipped').": ".$count[backup_cron_scheduled_helper::BACKUP_STATUS_SKIPPED]."\n";
+            $message .= "  ".get_string('error').": ".$count[backup_cron_scheduled_helper::BACKUP_STATUS_ERROR]."\n";
+            $message .= "  ".get_string('unfinished').": ".$count[backup_cron_scheduled_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("scheduledbackupstatus");
+
+            //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_sche_running
+        backup_cron_scheduled_helper::set_state_running(false);
+
+        mtrace('Scheduled backups complete.');
+
+        return $status;
+    }
+
+    /**
+     * Gets the results from the last scheduled 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 scheduled backup should be run.
+     *
+     * @param mixed $timezone
+     * @param int $now
+     * @return int
+     */
+    public static function calculate_next_scheduled_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
+        $scheduleddays = substr($config->backup_sche_weekdays,$date['wday']) . $config->backup_sche_weekdays;
+        $daysfromtoday = strpos($scheduleddays, "1");
+        if (empty($daysfromtoday)) {
+            $daysfromtoday = 1;
+        }
+
+        //If some day has been found
+        if ($daysfromtoday !== false) {
+            //Calculate distance
+            $dist = ($daysfromtoday * 86400) +                //Days distance
+                    ($config->backup_sche_hour * 3600) +      //Hours distance
+                    ($config->backup_sche_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_scheduled_backup($timezone, $result);
+        }
+
+        return $result;
+    }
+
+    /**
+     * Launches a scheduled backup routine for the given course
+     *
+     * @param stdClass $course
+     * @param int $starttime
+     * @param int $userid
+     * @return bool
+     */
+    public static function launch_scheduled_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_SCHEDULED, $userid);
+
+        try {
+
+            $settings = array(
+                'users' => 'backup_sche_users',
+                'role_assignments' => 'backup_sche_users',
+                'user_files' => 'backup_sche_user_files',
+                'activities' => 'backup_sche_activities',
+                'blocks' => 'backup_sche_blocks',
+                'filters' => 'backup_sche_filters',
+                'comments' => 'backup_sche_comments',
+                'completion_information' => 'backup_sche_userscompletion',
+                'logs' => 'backup_sche_logs',
+                'histories' => 'backup_sche_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_sche_destination;
+            $storage = (int)$config->backup_sche_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_sche_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 scheduled backup system.
+     *
+     * @global moodle_database $DB
+     * @return int One of self::STATE_*
+     */
+    public static function get_scheduled_backup_state() {
+        global $DB;
+
+        $config = get_config('backup');
+        if (empty($config->backup_sche_active)) {
+            return self::STATE_DISABLED;
+        } else if (!empty($config->backup_sche_running)) {
+            // TODO: We should find some way of checking whether the scheduled
+            // 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 scheduled backup system.
+     *
+     * @param bool $running
+     * @return bool
+     */
+    public static function set_state_running($running = true) {
+        if ($running === true) {
+            if (self::get_scheduled_backup_state() === self::STATE_RUNNING) {
+                throw new backup_exception('backup_scheduled_already_running');
+            }
+            set_config('backup_sche_running', '1', 'backup');
+        } else {
+            unset_config('backup_sche_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_sche_keep
+     *
+     * @param stdClass $course
+     * @return bool
+     */
+    public static function remove_excess_backups($course) {
+        $config = get_config('backup');
+        $keep =     (int)$config->backup_sche_keep;
+        $storage =  $config->backup_sche_storage;
+        $dir =      $config->backup_sche_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 = 'scheduled';
+            $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: moodle/backup/util/helper/backup_helper.class.php
--- moodle/backup/util/helper/backup_helper.class.php Base (1.15)
+++ moodle/backup/util/helper/backup_helper.class.php Locally Modified (Based On 1.15)
@@ -229,6 +229,11 @@
                 break;
         }
 
+        if ($backupmode == backup::MODE_SCHEDULED) {
+            // Scheduled backups have there own special area!
+            $filearea  = 'scheduled';
+        }
+
         // 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: moodle/backup/util/ui/renderer.php
--- moodle/backup/util/ui/renderer.php Base (1.18)
+++ moodle/backup/util/ui/renderer.php Locally Modified (Based On 1.18)
@@ -406,8 +406,8 @@
         $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: moodle/lang/en/backup.php
--- moodle/lang/en/backup.php Base (1.25)
+++ moodle/lang/en/backup.php Locally Modified (Based On 1.25)
@@ -54,6 +54,8 @@
 $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['choosefilefromscheduledbackup'] = 'Scheduled backups';
+$string['choosefilefromscheduledbackup_help'] = 'Contains backups created by the backup scheduling system.';
 $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 +189,13 @@
 $string['rootsettinguserscompletion'] = 'Include user completion details';
 $string['rootsettinglogs'] = 'Include course logs';
 $string['rootsettinggradehistories'] = 'Include grade history';
+$string['scheduledsetup'] = 'Scheduled backup setup';
 $string['scheduledsettings'] = 'Scheduled backup settings';
+$string['scheduledstorage'] = 'Scheduled backup storage';
+$string['scheduledstoragehelp'] = 'Choose the location where you want scheduled backups to be stored when they are created.';
+$string['storagecourseonly'] = 'Course backup filearea';
+$string['storagecourseandexternal'] = 'Course backup filearea and the specified directory';
+$string['storageexternalonly'] = 'Specified directory for scheduled backups';
 $string['sectionincanduser'] = 'Included in backup along with user information';
 $string['sectioninc'] = 'Included in backup (no user information)';
 $string['sectionactivities'] = 'Activities';
Index: moodle/lang/en/repository.php
--- moodle/lang/en/repository.php Base (1.30)
+++ moodle/lang/en/repository.php Locally Modified (Based On 1.30)
@@ -152,6 +152,7 @@
 $string['saveas'] = 'Save as';
 $string['saved'] = 'Saved';
 $string['saving'] = 'Saving';
+$string['scheduledbackup'] = 'Scheduled backups';
 $string['search'] = 'Search';
 $string['searching'] = 'Search in';
 $string['select'] = 'Select';
Index: moodle/lib/cronlib.php
--- moodle/lib/cronlib.php Base (1.11)
+++ moodle/lib/cronlib.php Locally Modified (Based On 1.11)
@@ -333,28 +333,12 @@
 
     } // 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 scheduled backups if required.
+    if (empty($CFG->disablescheduledbackups)) {
+        require_once($CFG->dirroot.'/backup/util/includes/backup_includes.php');
+        require_once($CFG->dirroot.'/backup/util/helper/backup_cron_helper.class.php');
+        backup_cron_scheduled_helper::run_scheduled_backup();
             }
-        }
-    }
 
 /// Run the auth cron, if any
 /// before enrolments because it might add users that will be needed in enrol plugins
Index: moodle/lib/db/access.php
--- moodle/lib/db/access.php Base (1.145)
+++ moodle/lib/db/access.php Locally Modified (Based On 1.145)
@@ -257,6 +257,14 @@
         'clonepermissionsfrom' =>  'moodle/restore:restorecourse'
     ),
 
+    'moodle/restore:restorescheduled' => 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: moodle/lib/db/messages.php
--- moodle/lib/db/messages.php Base (1.5)
+++ moodle/lib/db/messages.php Locally Modified (Based On 1.5)
@@ -41,4 +41,8 @@
     'instantmessage' => array (
     ),
 
+    'backup' => array (
+        'capability'  => 'moodle/site:config'
+    )
+
 );
Index: moodle/lib/db/upgrade.php
--- moodle/lib/db/upgrade.php Base (1.505)
+++ moodle/lib/db/upgrade.php Locally Modified (Based On 1.505)
@@ -5401,6 +5401,24 @@
         upgrade_main_savepoint(true, 2010110500);
     }
 
+    if ($oldversion < 2010110800) {
+        // Clean up the old scheduled backup settings that are no longer relevant
+
+        set_config('backup_sche_user', '1', 'backup');
+        set_config('backup_sche_user_files', get_config('backup_sche_userfiles', 'backup'), 'backup');
+        set_config('backup_sche_user_activities', get_config('backup_sche_modules', 'backup'), 'backup');
+
+        unset_config('backup_sche_userfiles', 'backup');
+        unset_config('backup_sche_modules', 'backup');
+        unset_config('backup_sche_coursefiles', 'backup');
+        unset_config('backup_sche_sitefiles', 'backup');
+        unset_config('backup_sche_withuserdata', 'backup');
+        unset_config('backup_sche_metacourse', 'backup');
+        unset_config('backup_sche_gradebook_history');
+
+        upgrade_main_savepoint(true, 2010110800);
+    }
+
     return true;
 }
 
Index: moodle/lib/filebrowser/file_info_context_course.php
--- moodle/lib/filebrowser/file_info_context_course.php Base (1.6)
+++ moodle/lib/filebrowser/file_info_context_course.php Locally Modified (Based On 1.6)
@@ -189,6 +189,44 @@
         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 scheduled backup filearea directory
+     *
+     * @param int $itemid
+     * @param string $filepath
+     * @param string $filename
+     * @return file_info_context_course 
+     */
+    protected function get_area_backup_scheduled($itemid, $filepath, $filename) {
+        global $CFG;
+
+        if (!has_capability('moodle/restore:restorescheduled', $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', 'scheduled', 0, $filepath, $filename)) {
+            if ($filepath === '/' and $filename === '.') {
+                $storedfile = new virtual_root_file($this->context->id, 'backup', 'scheduled', 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('scheduledbackup', 'repository'), true, $downloadable, $uploadable, false);
+    }
+
     protected function get_area_backup_section($itemid, $filepath, $filename) {
         global $CFG, $DB;
 
@@ -264,6 +302,9 @@
         if ($child = $this->get_area_backup_course(0, '/', '.')) {
             $children[] = $child;
         }
+        if ($child = $this->get_area_backup_scheduled(0, '/', '.')) {
+            $children[] = $child;
+        }
         if ($child = $this->get_area_course_legacy(0, '/', '.')) {
             $children[] = $child;
         }
Index: moodle/pluginfile.php
--- moodle/pluginfile.php Base (1.46)
+++ moodle/pluginfile.php Locally Modified (Based On 1.46)
@@ -633,6 +633,21 @@
         session_get_instance()->write_close();
         send_stored_file($file, 60*60, 0, $forcedownload);
 
+    } else if ($filearea === 'scheduled' and $context->contextlevel == CONTEXT_COURSE) {
+        // Backup files that were generated by the scheduled 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', 'scheduled', 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: moodle/theme/standard/style/core.css
--- moodle/theme/standard/style/core.css Base (1.23)
+++ moodle/theme/standard/style/core.css Locally Modified (Based On 1.23)
@@ -359,6 +359,11 @@
 .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;}
\ No newline at end of file
 
 /**
  * Site registration
Index: moodle/version.php
--- moodle/version.php Base (1.1830)
+++ moodle/version.php Locally Modified (Based On 1.1830)
@@ -29,7 +29,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$version = 2010110500;  // YYYYMMDD   = date of the last version bump
+$version = 2010110800;  // YYYYMMDD   = date of the last version bump
                         //         XX = daily increments
 
 $release = '2.0 RC1 (Build: 20101108)';  // Human-friendly version name
