# 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/sitebackup/index.php
--- moodle/admin/sitebackup/index.php No Base Revision
+++ moodle/admin/sitebackup/index.php Locally New
@@ -0,0 +1,44 @@
+<?php
+
+require_once('../../config.php');
+require_once($CFG->dirroot.'/backup/util/includes/backup_includes.php');
+require_once($CFG->dirroot.'/admin/sitebackup/lib.php');
+
+require_login();
+
+if (false) {
+    $PAGE = new moodle_page();
+    $OUTPUT = new core_renderer();
+    $DB = new moodle_database();
+}
+
+$PAGE->set_context(get_system_context());
+$PAGE->set_url(new moodle_url('/admin/sitebackup/index.php'));
+$PAGE->set_pagelayout('admin');
+$PAGE->set_title('Site backup');
+$PAGE->set_heading($PAGE->title);
+
+$progress = new progress_manager($PAGE);
+
+echo $OUTPUT->header();
+echo $OUTPUT->heading('Progress');
+echo html_writer::start_tag('div', array('id'=>'site-backup-progress'));
+echo html_writer::end_tag('div');
+echo $OUTPUT->footer();
+
+ob_flush();
+flush();
+
+sleep(2);
+
+remove_dir($CFG->dataroot . '/temp/sitebackup/');
+
+try {
+    admin_sitebackup::prepare_for_backup($progress);
+    admin_sitebackup::backup_database($progress);
+    admin_sitebackup::backup_moodledata($progress);
+    admin_sitebackup::backup_archive($progress);
+    $progress->notify('Site backup completed succesfully', 'siteback-outcome-success');
+} catch (moodle_exception $e) {
+    admin_sitebackup::cleanup_fail($progress);
+}
\ No newline at end of file
Index: moodle/admin/sitebackup/lib.php
--- moodle/admin/sitebackup/lib.php No Base Revision
+++ moodle/admin/sitebackup/lib.php Locally New
@@ -0,0 +1,243 @@
+<?php
+
+require_once($CFG->dirroot.'/backup/util/xml/contenttransformer/xml_contenttransformer.class.php');
+require_once($CFG->dirroot.'/lib/filestorage/zip_packer.php');
+
+abstract class admin_sitebackup {
+
+    static public function prepare_for_backup(progress_manager $progress) {
+        global $CFG;
+        $tasks = 1;
+        
+        $progress->start_task('Backup preperation', 'During this process the system will be prepared for the backup to occur.', 1);
+
+        $tmpdir = $CFG->dataroot . '/temp/sitebackup/database';
+        if (!check_dir_exists($tmpdir, true, true)) {
+            throw new moodle_exception('cannot_create_backup_temp_dir');
+        }
+
+        $tmpdir = $CFG->dataroot . '/temp/sitebackup/moodledata';
+        if (!check_dir_exists($tmpdir, true, true)) {
+            throw new moodle_exception('cannot_create_backup_temp_dir');
+        }
+
+        $progress->complete_task();
+        
+    }
+
+    static public function backup_moodledata(progress_manager $progress) {
+        global $CFG;
+
+        set_time_limit(60);
+
+        $files = array();
+        if ($dh = opendir($CFG->dataroot)) {
+            while (($file = readdir($dh)) !== false) {
+                if (strpos($file, '.')===0 || (is_dir($CFG->dataroot.'/'.$file) && ($file=='cache' || $file=='temp'))) {
+                    continue;
+                }
+                $files[$file] = $CFG->dataroot.'/'.$file;
+            }
+            closedir($dh);
+        }
+
+        $progress->start_task('Exporting moodledata directory', 'Currently your moodledata directory excluding the temp directory is being archived.', count($files));
+
+        $fp = get_file_packer('application/zip');
+        $fp->archive_to_pathname($files, $CFG->dataroot . '/temp/sitebackup/moodledata/data.zip');
+
+        $progress->complete_task();
+    }
+
+    static public function cleanup_fail($e, progress_manager $progress) {
+        global $CFG;
+        $progress->start_task('Cleanup', 'The system is cleaning up after a failed backup attempt.', 1);
+
+        remove_dir($CFG->dataroot . '/temp/sitebackup/');
+
+        $progress->complete_task();
+
+        echo html_writer::tag('div', $e->getMessage()."<br />".$e->getFile().': '.$e->getLine(), array('class'=>'siteback-outcome-error'));
+    }
+
+    /**
+     *
+     * @global moodle_database $DB
+     */
+    static public function backup_database(progress_manager $progress) {
+        global $CFG, $DB;
+
+        $tables = $DB->get_tables();
+
+        foreach ($tables as $table) {
+            $tables[$table] = $DB->count_records($table);
+        }
+
+        $progress->start_task('Exporting database', 'Currently all database tables are being exported to XML files.', array_sum($tables));
+
+        foreach (array_keys($tables) as $table) {
+
+            set_time_limit(60);
+
+            $fullpath = $CFG->dataroot . '/temp/sitebackup/database/table.'.$table.'.xml';
+
+            $fields = $DB->get_columns($table);
+
+            $structure = new backup_nested_element($table);
+            $rows = new backup_nested_element('rows');
+
+            $row = new sitebackup_nested_element('row', array(), array_keys($fields));
+            $row->set_progress_manager($progress);
+            $row->set_source_table($table, array());
+
+            $structure->add_child($rows);
+            $rows->add_child($row);
+
+            $xt = new sitebackup_transformer();
+
+            $xo = new file_xml_output($fullpath);
+            $xw = new xml_writer($xo, $xt);
+            $pr = new backup_structure_processor($xw);
+
+            $pr->set_var(backup::VAR_BACKUPID, 'sitebackup');
+            
+            $xw->start();
+            $structure->process($pr);
+            $xw->stop();
+
+        }
+
+        $progress->complete_task();
+    }
+
+    static public function backup_archive(progress_manager $progress) {
+        global $CFG, $USER;
+
+        $progress->start_task('Achiving exported data', 'Currently the exported database and moodeldata directory are being exported to your private files area.', 3);
+
+        $archive = $CFG->dataroot.'/temp/sitebackup/sitebackup.zip';
+        $files = array('sitebackup'=>$CFG->dataroot . '/temp/sitebackup');
+
+        $fp = get_file_packer('application/zip');
+        $fp->archive_to_pathname($files, $archive);
+
+        $progress->increment_progress();
+
+        remove_dir($CFG->dataroot . '/temp/sitebackup/moodledata');
+        remove_dir($CFG->dataroot . '/temp/sitebackup/database');
+        $progress->increment_progress();
+
+        $progress->complete_task();
+    }
+
+}
+
+class progress_manager {
+
+    /**
+     * @var moodle_page
+     */
+    protected $page;
+
+    protected $currenttask = null;
+
+    public function __construct(moodle_page $page) {
+        $this->page = $page;
+        $module = array(
+            'name' => 'sitebackup-manager',
+            'fullpath' => '/admin/sitebackup/module.js',
+            'requires' => array('base', 'node', 'dom')
+        );
+        $config = array(
+            array('nodeId'=>'site-backup-progress')
+        );
+        $page->requires->js_init_call('M.sitebackup.initProgressManager', $config, null, $module);
+        $page->requires->css('/admin/sitebackup/styles.css');
+    }
+
+    public function start_task($title, $description, $progresstoachieve, $currentprogress=0) {
+        $this->currenttask = new stdClass;
+        $this->currenttask->title = $title;
+        $this->currenttask->description = $description;
+        $this->currenttask->totalprogress = $progresstoachieve;
+        $this->currenttask->currentprogress = $currentprogress;
+        $this->currenttask->progress = round(($this->currenttask->currentprogress / $this->currenttask->totalprogress)*100, 0);
+        $this->currenttask->lastupdate = 0;
+
+        $javascript = 'M.sitebackup.startTask('.json_encode($this->currenttask).');';
+
+        echo html_writer::script($javascript);
+        ob_flush();
+        flush();
+    }
+
+    public function complete_task() {
+        $javascript = 'M.sitebackup.completeTask();';
+        echo html_writer::script($javascript);
+        ob_flush();
+        flush();
+    }
+
+    public function update_progress($currentprogress) {
+        $this->currenttask->currentprogress = $currentprogress;
+        $this->currenttask->progress = round(($this->currenttask->currentprogress / $this->currenttask->totalprogress)*100, 0);
+        if ($this->currenttask->progress > $this->currenttask->lastupdate) {
+            $javascript = 'M.sitebackup.updateProgress('.$this->currenttask->progress.');';
+            echo html_writer::script($javascript);
+            ob_flush();
+            flush();
+            $this->currenttask->lastupdate = $this->currenttask->progress;
+        }
+    }
+
+    public function increment_progress() {
+        $this->update_progress($this->currenttask->currentprogress+1);
+    }
+
+    public function notify($msg, $msgclass) {
+        $msg = htmlentities($msg, ENT_QUOTES);
+        $msgclass = htmlentities($msgclass, ENT_QUOTES);
+        $javascript = "M.sitebackup.notify('$msg', '$msgclass');";
+        echo html_writer::script($javascript);
+        ob_flush();
+        flush();
+    }
+
+}
+
+class sitebackup_transformer extends xml_contenttransformer {
+    public function process($content) {
+         // Array or object, debug and try our best recursively, shouldn't happen but...
+        if (is_array($content)) {
+            debugging('Backup XML transformer should process arrays but plain content always', DEBUG_DEVELOPER);
+            foreach($content as $key => $plaincontent) {
+                $content[$key] = $this->process($plaincontent);
+            }
+            return $content;
+        } else if (is_object($content)) {
+            debugging('Backup XML transformer should not process objects but plain content always', DEBUG_DEVELOPER);
+            foreach((array)$content as $key => $plaincontent) {
+                $content[$key] = $this->process($plaincontent);
+            }
+            return (object)$content;
+        }
+
+        if (is_null($content)) {  // Some cases we know we can skip complete processing
+            return '$@NULL@$';
+        }
+        return $content;
+    }
+}
+
+class sitebackup_nested_element extends backup_nested_element {
+    protected $progressmanager = null;
+    public function set_progress_manager(progress_manager $progress) {
+        $this->progressmanager = $progress;
+    }
+    public function clean_values() {
+        parent::clean_values();
+        if ($this->progressmanager !== null) {
+            $this->progressmanager->increment_progress();
+        }
+    }
+}
Index: moodle/admin/sitebackup/module.js
--- moodle/admin/sitebackup/module.js No Base Revision
+++ moodle/admin/sitebackup/module.js Locally New
@@ -0,0 +1,112 @@
+YUI.add('sitebackup-manager', function(Y) {
+
+var C = Y.Node.create
+
+var MANAGER = function(config) {
+    MANAGER.superclass.constructor.apply(this, arguments);
+}
+MANAGER.prototype = {
+    _node : null,
+    _currentTask : null,
+    initializer : function(config) {
+        var nid = this.get('nodeId');
+        this._node = Y.one('#'+nid);
+    },
+    startTask : function(task) {
+        if (this._currentTask !== null) {
+            this.completeTask();
+        }
+        this._currentTask = new TASK(task);
+        this._currentTask.set('manager', this);
+        this._node.append(this._currentTask._node);
+    },
+    updateProgress : function(progress) {
+        this._currentTask.set('progress', progress);
+    },
+    completeTask : function() {
+        this._currentTask.set('progress', 100);
+        delete this._currentTask;
+        this._currentTask = null;
+    },
+    notify : function(message, messageclass) {
+        this._node.append(C('<div class="'+messageclass+'">'+message+'</div>'));
+    }
+}
+Y.extend(MANAGER, Y.Base, MANAGER.prototype, {
+    NAME : 'progress-manager',
+    ATTRS : {
+        nodeId : {}
+    }
+});
+
+var TASK = function(config) {
+    TASK.superclass.constructor.apply(this, arguments);
+}
+TASK.prototype = {
+    _node : null,
+    initializer : function() {
+        this._node = C('<div class="progress-task"></div>')
+                        .append(C('<div class="progress-task-title">'+this.get('title')+'</div>'))
+                        .append(C('<div class="progress-task-description">'+this.get('description')+'</div>'))
+                        .append(C('<div class="progress-task-bar"><div class="progress-task-bar-complete"></div></div>'))
+                        .append(C('<div class="progress-task-progress">'+this.get('progressStr')+'</div>'));
+        this.after('progressChange', this._updateProgress, this);
+    },
+    _updateProgress : function() {
+        var p = this.get('progress');
+        this._node.one('.progress-task-bar-complete').setStyle('width', p+'%');
+        this._node.one('.progress-task-progress').setContent(this.get('progressStr'));
+        if (p > 99) {
+            this._node.addClass('progress-task-complete').one('.progress-task-bar-complete').setContent('complete');
+        }
+    }
+}
+Y.extend(TASK, Y.Base, TASK.prototype, {
+    NAME : 'progress-task',
+    ATTRS : {
+        manager : {},
+        title : {},
+        description : {},
+        progress : {
+            value : 0,
+            setter : function(newval) {
+                newval = Math.round(parseInt(newval));
+                return (newval > 100)?100:newval;
+            }
+        },
+        progressStr : {
+            readOnly :true,
+            getter : function() {
+                var value = this.get('progress').toString();
+                var vallen = value.length;
+                if (vallen >= 2) {
+                    return value + ' %';
+                } else if (vallen == 1 && value !== '0') {
+                    return '0'+value  + ' %';
+                } else {
+                    return '0 %';
+                }
+            }
+        }
+    }
+});
+
+M.sitebackup = M.sitebackup || {};
+M.sitebackup._progressManager = null;
+M.sitebackup.initProgressManager = function(y, config) {
+    this._progressManager = new MANAGER(config);
+}
+M.sitebackup.startTask = function(task) {
+    this._progressManager.startTask(task);
+}
+M.sitebackup.updateProgress = function(progress) {
+    this._progressManager.updateProgress(progress);
+}
+M.sitebackup.completeTask = function() {
+    this._progressManager.completeTask();
+}
+M.sitebackup.notify = function(msg, msgclass) {
+    this._progressManager.notify(msg, msgclass);
+}
+
+}, '@VERSION@', {'requires':['base','node','dom']});
\ No newline at end of file
Index: moodle/admin/sitebackup/styles.css
--- moodle/admin/sitebackup/styles.css No Base Revision
+++ moodle/admin/sitebackup/styles.css Locally New
@@ -0,0 +1,13 @@
+.progress-task {border:1px solid #bbb;background-color:#f3f3f3;width:80%;margin:1em auto 0 auto;}
+.progress-task .progress-task-title {font-size:12pt;font-weight:bold;text-align:center;color:#111;margin:5px 4px 4px 4px;}
+.progress-task .progress-task-progress,
+.progress-task .progress-task-description {font-size:9pt;text-align:center;color:#444;}
+.progress-task .progress-task-bar {width:90%;border:1px solid #111;height:20px;margin:0.5em auto;}
+.progress-task .progress-task-bar-complete {width:0;height:20px;background-color:#37749E;font-size:1px;margin:0;padding: 0;}
+
+
+.progress-task.progress-task-complete .progress-task-bar-complete {background-color:#429E37;color:#FFF;font-size:16px;line-height:19px;text-align:center;font-weight:bold;}
+.progress-task.progress-task-complete  .progress-task-progress {display:none;}
+
+.siteback-outcome-success {font-size:12pt;margin:3em;font-weight:bold;color:#111;text-align:center;}
+.siteback-outcome-error {font-size:11pt;margin-top:2em;font-weight:bold;color:#AA0000;text-align:center;}
\ No newline at end of file
