first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+607
View File
@@ -0,0 +1,607 @@
// 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/>.
/**
* This module updates the UI during an asynchronous
* backup or restore process.
*
* @module core_backup/async_backup
* @copyright 2018 Matt Porritt <mattp@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.7
*/
define(['jquery', 'core/ajax', 'core/str', 'core/notification', 'core/templates'],
function($, ajax, Str, notification, Templates) {
/**
* Module level constants.
*
* Using var instead of const as ES6 isn't fully supported yet.
*/
var STATUS_EXECUTING = 800;
var STATUS_FINISHED_ERR = 900;
var STATUS_FINISHED_OK = 1000;
/**
* Module level variables.
*/
var Asyncbackup = {};
var checkdelayoriginal = 15000; // This is the default time to use.
var checkdelay = 15000; // How often we should check for progress updates.
var checkdelaymultipler = 1.5; // If a request fails this multiplier will be used to increase the checkdelay value
var backupid; // The backup id to get the progress for.
var contextid; // The course this backup progress is for.
var restoreurl; // The URL to view course restores.
var typeid; // The type of operation backup or restore.
var backupintervalid; // The id of the setInterval function.
var allbackupintervalid; // The id of the setInterval function.
var allcopyintervalid; // The id of the setInterval function.
var timeout = 2000; // Timeout for ajax requests.
/**
* Helper function to update UI components.
*
* @param {string} backupid The id to match elements on.
* @param {string} type The type of operation, backup or restore.
* @param {number} percentage The completion percentage to apply.
*/
function updateElement(backupid, type, percentage) {
var percentagewidth = Math.round(percentage) + '%';
var elementbar = document.querySelectorAll("[data-" + type + "id=" + CSS.escape(backupid) + "]")[0];
var percentagetext = percentage.toFixed(2) + '%';
// Set progress bar percentage indicators
elementbar.setAttribute('aria-valuenow', percentagewidth);
elementbar.style.width = percentagewidth;
elementbar.innerHTML = percentagetext;
}
/**
* Updates the interval we use to check for backup progress.
*
* @param {Number} intervalid The id of the interval
* @param {Function} callback The function to use in setInterval
* @param {Number} value The specified interval (in milliseconds)
* @returns {Number}
*/
function updateInterval(intervalid, callback, value) {
clearInterval(intervalid);
return setInterval(callback, value);
}
/**
* Update backup table row when an async backup completes.
*
* @param {string} backupid The id to match elements on.
*/
function updateBackupTableRow(backupid) {
var statuscell = $('#' + backupid + '_bar').parent().parent();
var tablerow = statuscell.parent();
var cellsiblings = statuscell.siblings();
var timecell = cellsiblings[1];
var timevalue = $(timecell).text();
var filenamecell = cellsiblings[0];
var filename = $(filenamecell).text();
ajax.call([{
// Get the table data via webservice.
methodname: 'core_backup_get_async_backup_links_backup',
args: {
'filename': filename,
'contextid': contextid,
'backupid': backupid
},
}])[0].done(function(response) {
// We have the data now update the UI.
var context = {
filename: filename,
time: timevalue,
size: response.filesize,
fileurl: response.fileurl,
restoreurl: response.restoreurl
};
Templates.render('core/async_backup_progress_row', context).then(function(html, js) {
Templates.replaceNodeContents(tablerow, html, js);
return;
}).fail(function() {
notification.exception(new Error('Failed to load table row'));
return;
});
});
}
/**
* Update restore table row when an async restore completes.
*
* @param {string} backupid The id to match elements on.
*/
function updateRestoreTableRow(backupid) {
var statuscell = $('#' + backupid + '_bar').parent().parent();
var tablerow = statuscell.parent();
var cellsiblings = statuscell.siblings();
var coursecell = cellsiblings[0];
var timecell = cellsiblings[1];
var timevalue = $(timecell).text();
ajax.call([{
// Get the table data via webservice.
methodname: 'core_backup_get_async_backup_links_restore',
args: {
'backupid': backupid,
'contextid': contextid
},
}])[0].done(function(response) {
// We have the data now update the UI.
var resourcename = $(coursecell).text();
var context = {
resourcename: resourcename,
restoreurl: response.restoreurl,
time: timevalue
};
Templates.render('core/async_restore_progress_row', context).then(function(html, js) {
Templates.replaceNodeContents(tablerow, html, js);
return;
}).fail(function() {
notification.exception(new Error('Failed to load table row'));
return;
});
});
}
/**
* Update copy table row when an course copy completes.
*
* @param {string} backupid The id to match elements on.
*/
function updateCopyTableRow(backupid) {
var elementbar = document.querySelectorAll("[data-restoreid=" + CSS.escape(backupid) + "]")[0];
var restorecourse = elementbar.closest('tr').children[1];
var coursename = restorecourse.innerHTML;
var courselink = document.createElement('a');
var elementbarparent = elementbar.closest('td');
var operation = elementbarparent.previousElementSibling;
// Replace the prgress bar.
Str.get_string('complete').then(function(content) {
operation.innerHTML = content;
return;
}).catch(function() {
notification.exception(new Error('Failed to load string: complete'));
return;
});
Templates.render('core/async_copy_complete_cell', {}).then(function(html, js) {
Templates.replaceNodeContents(elementbarparent, html, js);
return;
}).fail(function() {
notification.exception(new Error('Failed to load table cell'));
return;
});
// Update the destination course name to a link to that course.
ajax.call([{
methodname: 'core_backup_get_async_backup_links_restore',
args: {
'backupid': backupid,
'contextid': 0
},
}])[0].done(function(response) {
courselink.setAttribute('href', response.restoreurl);
courselink.innerHTML = coursename;
restorecourse.innerHTML = null;
restorecourse.appendChild(courselink);
return;
}).fail(function() {
notification.exception(new Error('Failed to update table row'));
return;
});
}
/**
* Update the Moodle user interface with the progress of
* the backup process.
*
* @param {object} progress The progress and status of the process.
*/
function updateProgress(progress) {
var percentage = progress.progress * 100;
var type = 'backup';
var elementbar = document.querySelectorAll("[data-" + type + "id=" + CSS.escape(backupid) + "]")[0];
var elementstatus = $('#' + backupid + '_status');
var elementdetail = $('#' + backupid + '_detail');
var elementbutton = $('#' + backupid + '_button');
var stringRequests;
if (progress.status == STATUS_EXECUTING) {
// Process is in progress.
// Add in progress class color to bar.
elementbar.classList.add('bg-success');
updateElement(backupid, type, percentage);
// Change heading.
var strProcessing = 'async' + typeid + 'processing';
Str.get_string(strProcessing, 'backup').then(function(title) {
elementstatus.text(title);
return;
}).catch(function() {
notification.exception(new Error('Failed to load string: backup ' + strProcessing));
});
} else if (progress.status == STATUS_FINISHED_ERR) {
// Process completed with error.
// Add in fail class color to bar.
elementbar.classList.add('bg-danger');
// Remove in progress class color to bar.
elementbar.classList.remove('bg-success');
updateElement(backupid, type, 100);
// Change heading and text.
var strStatus = 'async' + typeid + 'error';
var strStatusDetail = 'async' + typeid + 'errordetail';
stringRequests = [
{key: strStatus, component: 'backup'},
{key: strStatusDetail, component: 'backup'}
];
Str.get_strings(stringRequests).then(function(strings) {
elementstatus.text(strings[0]);
elementdetail.text(strings[1]);
return;
})
.catch(function() {
notification.exception(new Error('Failed to load string'));
return;
});
$('.backup_progress').children('span').removeClass('backup_stage_current');
$('.backup_progress').children('span').last().addClass('backup_stage_current');
// Stop checking when we either have an error or a completion.
clearInterval(backupintervalid);
} else if (progress.status == STATUS_FINISHED_OK) {
// Process completed successfully.
// Add in progress class color to bar
elementbar.classList.add('bg-success');
updateElement(backupid, type, 100);
// Change heading and text
var strComplete = 'async' + typeid + 'complete';
Str.get_string(strComplete, 'backup').then(function(title) {
elementstatus.text(title);
return;
}).catch(function() {
notification.exception(new Error('Failed to load string: backup ' + strComplete));
});
if (typeid == 'restore') {
ajax.call([{
// Get the table data via webservice.
methodname: 'core_backup_get_async_backup_links_restore',
args: {
'backupid': backupid,
'contextid': contextid
},
}])[0].done(function(response) {
var strDetail = 'async' + typeid + 'completedetail';
var strButton = 'async' + typeid + 'completebutton';
var stringRequests = [
{key: strDetail, component: 'backup', param: response.restoreurl},
{key: strButton, component: 'backup'}
];
Str.get_strings(stringRequests).then(function(strings) {
elementdetail.html(strings[0]);
elementbutton.text(strings[1]);
elementbutton.attr('href', response.restoreurl);
return;
})
.catch(function() {
notification.exception(new Error('Failed to load string'));
return;
});
});
} else {
var strDetail = 'async' + typeid + 'completedetail';
var strButton = 'async' + typeid + 'completebutton';
stringRequests = [
{key: strDetail, component: 'backup', param: restoreurl},
{key: strButton, component: 'backup'}
];
Str.get_strings(stringRequests).then(function(strings) {
elementdetail.html(strings[0]);
elementbutton.text(strings[1]);
elementbutton.attr('href', restoreurl);
return;
})
.catch(function() {
notification.exception(new Error('Failed to load string'));
return;
});
}
$('.backup_progress').children('span').removeClass('backup_stage_current');
$('.backup_progress').children('span').last().addClass('backup_stage_current');
// Stop checking when we either have an error or a completion.
clearInterval(backupintervalid);
}
}
/**
* Update the Moodle user interface with the progress of
* all the pending processes for backup and restore operations.
*
* @param {object} progress The progress and status of the process.
*/
function updateProgressAll(progress) {
progress.forEach(function(element) {
var percentage = element.progress * 100;
var backupid = element.backupid;
var type = element.operation;
var elementbar = document.querySelectorAll("[data-" + type + "id=" + CSS.escape(backupid) + "]")[0];
if (element.status == STATUS_EXECUTING) {
// Process is in element.
// Add in element class color to bar
elementbar.classList.add('bg-success');
updateElement(backupid, type, percentage);
} else if (element.status == STATUS_FINISHED_ERR) {
// Process completed with error.
// Add in fail class color to bar
elementbar.classList.add('bg-danger');
elementbar.classList.add('complete');
// Remove in element class color to bar
elementbar.classList.remove('bg-success');
updateElement(backupid, type, 100);
} else if (element.status == STATUS_FINISHED_OK) {
// Process completed successfully.
// Add in element class color to bar
elementbar.classList.add('bg-success');
elementbar.classList.add('complete');
updateElement(backupid, type, 100);
// We have a successful backup. Update the UI with download and file details.
if (type == 'backup') {
updateBackupTableRow(backupid);
} else {
updateRestoreTableRow(backupid);
}
}
});
}
/**
* Update the Moodle user interface with the progress of
* all the pending processes for copy operations.
*
* @param {object} progress The progress and status of the process.
*/
function updateProgressCopy(progress) {
progress.forEach(function(element) {
var percentage = element.progress * 100;
var backupid = element.backupid;
var type = element.operation;
var elementbar = document.querySelectorAll("[data-" + type + "id=" + CSS.escape(backupid) + "]")[0];
if (type == 'restore') {
let restorecell = elementbar.closest('tr').children[3];
Str.get_string('restore').then(function(content) {
restorecell.innerHTML = content;
return;
}).catch(function() {
notification.exception(new Error('Failed to load string: restore'));
});
}
if (element.status == STATUS_EXECUTING) {
// Process is in element.
// Add in element class color to bar
elementbar.classList.add('bg-success');
updateElement(backupid, type, percentage);
} else if (element.status == STATUS_FINISHED_ERR) {
// Process completed with error.
// Add in fail class color to bar
elementbar.classList.add('bg-danger');
elementbar.classList.add('complete');
// Remove in element class color to bar
elementbar.classList.remove('bg-success');
updateElement(backupid, type, 100);
} else if ((element.status == STATUS_FINISHED_OK) && (type == 'restore')) {
// Process completed successfully.
// Add in element class color to bar
elementbar.classList.add('bg-success');
elementbar.classList.add('complete');
updateElement(backupid, type, 100);
// We have a successful copy. Update the UI link to copied course.
updateCopyTableRow(backupid);
}
});
}
/**
* Get the progress of the backup process via ajax.
*/
function getBackupProgress() {
ajax.call([{
// Get the backup progress via webservice.
methodname: 'core_backup_get_async_backup_progress',
args: {
'backupids': [backupid],
'contextid': contextid
},
}], true, true, false, timeout)[0].done(function(response) {
// We have the progress now update the UI.
updateProgress(response[0]);
checkdelay = checkdelayoriginal;
backupintervalid = updateInterval(backupintervalid, getBackupProgress, checkdelayoriginal);
}).fail(function() {
checkdelay = checkdelay * checkdelaymultipler;
backupintervalid = updateInterval(backupintervalid, getBackupProgress, checkdelay);
});
}
/**
* Get the progress of all backup processes via ajax.
*/
function getAllBackupProgress() {
var backupids = [];
var progressbars = $('.progress').find('.progress-bar').not('.complete');
progressbars.each(function() {
backupids.push((this.id).substring(0, 32));
});
if (backupids.length > 0) {
ajax.call([{
// Get the backup progress via webservice.
methodname: 'core_backup_get_async_backup_progress',
args: {
'backupids': backupids,
'contextid': contextid
},
}], true, true, false, timeout)[0].done(function(response) {
updateProgressAll(response);
checkdelay = checkdelayoriginal;
allbackupintervalid = updateInterval(allbackupintervalid, getAllBackupProgress, checkdelayoriginal);
}).fail(function() {
checkdelay = checkdelay * checkdelaymultipler;
allbackupintervalid = updateInterval(allbackupintervalid, getAllBackupProgress, checkdelay);
});
} else {
clearInterval(allbackupintervalid); // No more progress bars to update, stop checking.
}
}
/**
* Get the progress of all copy processes via ajax.
*/
function getAllCopyProgress() {
var copyids = [];
var progressbars = $('.progress').find('.progress-bar[data-operation][data-backupid][data-restoreid]').not('.complete');
progressbars.each(function() {
let progressvars = {
'backupid': this.dataset.backupid,
'restoreid': this.dataset.restoreid,
'operation': this.dataset.operation,
};
copyids.push(progressvars);
});
if (copyids.length > 0) {
ajax.call([{
// Get the copy progress via webservice.
methodname: 'core_backup_get_copy_progress',
args: {
'copies': copyids
},
}], true, true, false, timeout)[0].done(function(response) {
updateProgressCopy(response);
checkdelay = checkdelayoriginal;
allcopyintervalid = updateInterval(allcopyintervalid, getAllCopyProgress, checkdelayoriginal);
}).fail(function() {
checkdelay = checkdelay * checkdelaymultipler;
allcopyintervalid = updateInterval(allcopyintervalid, getAllCopyProgress, checkdelay);
});
} else {
clearInterval(allcopyintervalid); // No more progress bars to update, stop checking.
}
}
/**
* Get status updates for all backups.
*
* @public
* @param {number} context The context id.
*/
Asyncbackup.asyncBackupAllStatus = function(context) {
contextid = context;
allbackupintervalid = setInterval(getAllBackupProgress, checkdelay);
};
/**
* Get status updates for all course copies.
*
* @public
*/
Asyncbackup.asyncCopyAllStatus = function() {
allcopyintervalid = setInterval(getAllCopyProgress, checkdelay);
};
/**
* Get status updates for backup.
*
* @public
* @param {string} backup The backup record id.
* @param {number} context The context id.
* @param {string} restore The restore link.
* @param {string} type The operation type (backup or restore).
*/
Asyncbackup.asyncBackupStatus = function(backup, context, restore, type) {
backupid = backup;
contextid = context;
restoreurl = restore;
if (type == 'backup') {
typeid = 'backup';
} else {
typeid = 'restore';
}
// Remove the links from the progress bar, no going back now.
$('.backup_progress').children('a').removeAttr('href');
// Periodically check for progress updates and update the UI as required.
backupintervalid = setInterval(getBackupProgress, checkdelay);
};
return Asyncbackup;
});
+134
View File
@@ -0,0 +1,134 @@
<?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/>.
/**
* This file contains the generic moodleform bridge for the backup user interface
* as well as the individual forms that relate to the different stages the user
* interface can exist within.
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Backup moodleform bridge
*
* Ahhh the mighty moodleform bridge! Strong enough to take the weight of 682 full
* grown african swallows all of whom have been carring coconuts for several days.
* EWWWWW!!!!!!!!!!!!!!!!!!!!!!!!
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class backup_moodleform extends base_moodleform {
/**
* Creates the form
*
* Overridden for type hinting on the first arg.
*
* @param backup_ui_stage $uistage
* @param moodle_url|string $action
* @param mixed $customdata
* @param string $method get|post
* @param string $target
* @param array $attributes
* @param bool $editable
*/
public function __construct(backup_ui_stage $uistage, $action = null, $customdata = null, $method = 'post',
$target = '', $attributes = null, $editable = true) {
parent::__construct($uistage, $action, $customdata, $method, $target, $attributes, $editable);
}
}
/**
* Initial backup user interface stage moodleform.
*
* Nothing to override we only need it defined so that moodleform doesn't get confused
* between stages.
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class backup_initial_form extends backup_moodleform {}
/**
* Schema backup user interface stage moodleform.
*
* Nothing to override we only need it defined so that moodleform doesn't get confused
* between stages.
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class backup_schema_form extends backup_moodleform {}
/**
* Confirmation backup user interface stage moodleform.
*
* Nothing to override we only need it defined so that moodleform doesn't get confused
* between stages.
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class backup_confirmation_form extends backup_moodleform {
/**
* Adds the last elements, rules, settings etc to the form after data has been set.
*
* We override this to add a rule and type to the filename setting.
*
* @throws coding_exception
*/
public function definition_after_data() {
parent::definition_after_data();
$this->_form->addRule('setting_root_filename', get_string('errorfilenamerequired', 'backup'), 'required');
$this->_form->setType('setting_root_filename', PARAM_FILE);
}
/**
* Validates the form.
*
* Relies on the parent::validation for the bulk of the work.
*
* @param array $data
* @param array $files
* @return array
* @throws coding_exception
*/
public function validation($data, $files) {
$errors = parent::validation($data, $files);
if (!array_key_exists('setting_root_filename', $errors)) {
if (trim($data['setting_root_filename']) == '') {
$errors['setting_root_filename'] = get_string('errorfilenamerequired', 'backup');
} else if (strlen(trim($data['setting_root_filename'])) > 255) {
$errors['setting_root_filename'] = get_string('errorfilenametoolong', 'backup');
} else if (!preg_match('#\.mbz$#i', $data['setting_root_filename'])) {
$errors['setting_root_filename'] = get_string('errorfilenamemustbezip', 'backup');
}
}
return $errors;
}
}
+225
View File
@@ -0,0 +1,225 @@
<?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/>.
/**
* This file contains the backup user interface class
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* This is the backup user interface class
*
* The backup user interface class manages the user interface and backup for Moodle.
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class backup_ui extends base_ui {
/**
* The stages of the backup user interface.
* The initial stage of the backup - settings are here.
*/
const STAGE_INITIAL = 1;
/**
* The stages of the backup user interface.
* The schema stage of the backup - here you choose the bits you include.
*/
const STAGE_SCHEMA = 2;
/**
* The stages of the backup user interface.
* The confirmation stage of the backup.
*/
const STAGE_CONFIRMATION = 4;
/**
* The stages of the backup user interface.
* The final stage of the backup - where it is being processed.
*/
const STAGE_FINAL = 8;
/**
* The stages of the backup user interface.
* The backup is now complete.
*/
const STAGE_COMPLETE = 16;
/**
* If set to true the current stage is skipped.
* @var bool
*/
protected static $skipcurrentstage = false;
/**
* Intialises what ever stage is requested. If none are requested we check
* params for 'stage' and default to initial
*
* @param int $stage The desired stage to intialise or null for the default
* @param array $params
* @return backup_ui_stage_initial|backup_ui_stage_schema|backup_ui_stage_confirmation|backup_ui_stage_final
*/
protected function initialise_stage($stage = null, array $params = null) {
if ($stage == null) {
$stage = optional_param('stage', self::STAGE_INITIAL, PARAM_INT);
}
if (self::$skipcurrentstage) {
$stage *= 2;
}
switch ($stage) {
case backup_ui::STAGE_INITIAL:
$stage = new backup_ui_stage_initial($this, $params);
break;
case backup_ui::STAGE_SCHEMA:
$stage = new backup_ui_stage_schema($this, $params);
break;
case backup_ui::STAGE_CONFIRMATION:
$stage = new backup_ui_stage_confirmation($this, $params);
break;
case backup_ui::STAGE_FINAL:
$stage = new backup_ui_stage_final($this, $params);
break;
default:
$stage = false;
break;
}
return $stage;
}
/**
* Returns the backup id
* @return string
*/
public function get_uniqueid() {
return $this->get_backupid();
}
/**
* Gets the backup id from the controller
* @return string
*/
public function get_backupid() {
return $this->controller->get_backupid();
}
/**
* Executes the backup plan
* @throws backup_ui_exception when the steps are wrong.
* @return bool
*/
public function execute() {
if ($this->progress >= self::PROGRESS_EXECUTED) {
throw new backup_ui_exception('backupuialreadyexecuted');
}
if ($this->stage->get_stage() < self::STAGE_FINAL) {
throw new backup_ui_exception('backupuifinalisedbeforeexecute');
}
$this->progress = self::PROGRESS_EXECUTED;
$this->controller->finish_ui();
$this->controller->execute_plan();
$this->stage = new backup_ui_stage_complete($this, $this->stage->get_params(), $this->controller->get_results());
return true;
}
/**
* Loads the backup controller if we are tracking one
* @param string $backupid
* @return backup_controller|false
*/
final public static function load_controller($backupid = false) {
// Get the backup id optional param.
if ($backupid) {
try {
// Try to load the controller with it.
// If it fails at this point it is likely because this is the first load.
$controller = backup_controller::load_controller($backupid);
return $controller;
} catch (Exception $e) {
return false;
}
}
return $backupid;
}
/**
* Gets an array of progress bar items that can be displayed through the backup renderer.
* @return array Array of items for the progress bar
*/
public function get_progress_bar() {
global $PAGE;
$stage = self::STAGE_COMPLETE;
$currentstage = $this->stage->get_stage();
$items = array();
while ($stage > 0) {
$classes = array('backup_stage');
if (floor($stage / 2) == $currentstage) {
$classes[] = 'backup_stage_next';
} else if ($stage == $currentstage) {
$classes[] = 'backup_stage_current';
} else if ($stage < $currentstage) {
$classes[] = 'backup_stage_complete';
}
$item = array('text' => strlen(decbin($stage)).'. '.get_string('currentstage'.$stage, 'backup'), 'class' => join(' ', $classes));
if ($stage < $currentstage && $currentstage < self::STAGE_COMPLETE && (!self::$skipcurrentstage || ($stage * 2) != $currentstage)) {
$params = $this->stage->get_params();
if (empty($params)) {
$params = array();
}
$params = array_merge($params, array('backup' => $this->get_backupid(), 'stage' => $stage));
$item['link'] = new moodle_url($PAGE->url, $params);
}
array_unshift($items, $item);
$stage = floor($stage / 2);
}
return $items;
}
/**
* Gets the name related to the operation of this UI
* @return string
*/
public function get_name() {
return 'backup';
}
/**
* Gets the id of the first stage this UI is reponsible for
* @return int
*/
public function get_first_stage_id() {
return self::STAGE_INITIAL;
}
/**
* If called with default arg the current stage gets skipped.
* @static
* @param bool $setting Set to true (default) if you want to skip this stage, false otherwise.
*/
public static function skip_current_stage($setting = true) {
self::$skipcurrentstage = $setting;
}
}
/**
* Backup user interface exception. Modelled off the backup_exception class
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class backup_ui_exception extends base_ui_exception {}
+787
View File
@@ -0,0 +1,787 @@
<?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/>.
/**
* This file contains the setting user interface classes that all backup/restore
* settings use to represent the UI they have.
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Abstract class used to represent the user interface that a setting has.
*
* @todo extend as required for restore
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class base_setting_ui {
/**
* Prefix applied to all inputs/selects
*/
const NAME_PREFIX = 'setting_';
/**
* The name of the setting
* @var string
*/
protected $name;
/**
* The label for the setting
* @var string
*/
protected $label;
/**
* An array of HTML attributes to apply to this setting
* @var array
*/
protected $attributes = array();
/**
* The backup_setting UI type this relates to. One of backup_setting::UI_*;
* @var int
*/
protected $type;
/**
* An icon to display next to this setting in the UI
* @var pix_icon
*/
protected $icon = false;
/**
* The setting this UI belongs to (parent reference)
* @var base_setting|backup_setting
*/
protected $setting;
/**
* Constructors are sooooo cool
* @param base_setting $setting
*/
public function __construct(base_setting $setting) {
$this->setting = $setting;
}
/**
* Destroy all circular references. It helps PHP 5.2 a lot!
*/
public function destroy() {
// No need to destroy anything recursively here, direct reset.
$this->setting = null;
}
/**
* Gets the name of this item including its prefix
* @return string
*/
public function get_name() {
return self::NAME_PREFIX.$this->name;
}
/**
* Gets the name of this item including its prefix
* @return string
*/
public function get_label() {
return $this->label;
}
/**
* Gets the type of this element
* @return int
*/
public function get_type() {
return $this->type;
}
/**
* Gets the HTML attributes for this item
* @return array
*/
public function get_attributes() {
return $this->attributes;
}
/**
* Gets the value of this setting
* @return mixed
*/
public function get_value() {
return $this->setting->get_value();
}
/**
* Gets the value to display in a static quickforms element
* @return mixed
*/
public function get_static_value() {
return $this->setting->get_value();
}
/**
* Gets the the PARAM_XXXX validation to be applied to the setting
*
* return string The PARAM_XXXX constant of null if the setting type is not defined
*/
public function get_param_validation() {
return $this->setting->get_param_validation();
}
/**
* Sets the label.
*
* @throws base_setting_ui_exception when the label is not valid.
* @param string $label
*/
public function set_label(string $label): void {
// Let's avoid empty/whitespace-only labels, so the html clean (that makes trim()) doesn't fail.
if (trim($label) === '') {
$label = '&nbsp;'; // Will be converted to non-breaking utf-8 char 0xc2a0 by PARAM_CLEANHTML.
}
$label = clean_param($label, PARAM_CLEANHTML);
if ($label === '') {
throw new base_setting_ui_exception('setting_invalid_ui_label');
}
$this->label = $label;
}
/**
* Disables the UI for this element
*/
public function disable() {
$this->attributes['disabled'] = 'disabled';
}
/**
* Sets the icon to display next to this item
*
* @param pix_icon $icon
*/
public function set_icon(pix_icon $icon) {
$this->icon = $icon;
}
/**
* Returns the icon to display next to this item, or false if there isn't one.
*
* @return pix_icon|false
*/
public function get_icon() {
if (!empty($this->icon)) {
return $this->icon;
}
return false;
}
}
/**
* Abstract class to represent the user interface backup settings have
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class backup_setting_ui extends base_setting_ui {
/**
* An array of options relating to this setting
* @var array
*/
protected $options = array();
/**
* JAC... Just Another Constructor
*
* @param backup_setting $setting
* @param string $label The label to display with the setting ui
* @param array $attributes Array of HTML attributes to apply to the element
* @param array $options Array of options to apply to the setting ui object
*/
public function __construct(backup_setting $setting, $label = null, array $attributes = null, array $options = null) {
parent::__construct($setting);
// Improve the inputs name by appending the level to the name.
switch ($setting->get_level()) {
case backup_setting::ROOT_LEVEL :
$this->name = 'root_'.$setting->get_name();
break;
case backup_setting::COURSE_LEVEL :
$this->name = 'course_'.$setting->get_name();
break;
case backup_setting::SECTION_LEVEL :
$this->name = 'section_'.$setting->get_name();
break;
case backup_setting::ACTIVITY_LEVEL :
$this->name = 'activity_'.$setting->get_name();
break;
}
$this->label = $label;
if (is_array($attributes)) {
$this->attributes = $attributes;
}
if (is_array($options)) {
$this->options = $options;
}
}
/**
* Creates a new backup setting ui based on the setting it is given
*
* @throws backup_setting_ui_exception if the setting type is not supported,
* @param backup_setting $setting
* @param int $type The backup_setting UI type. One of backup_setting::UI_*;
* @param string $label The label to display with the setting ui
* @param array $attributes Array of HTML attributes to apply to the element
* @param array $options Array of options to apply to the setting ui object
* @return backup_setting_ui_text|backup_setting_ui_checkbox|backup_setting_ui_select|backup_setting_ui_radio
*/
final public static function make(backup_setting $setting, $type, $label, array $attributes = null, array $options = null) {
// Base the decision we make on the type that was sent.
switch ($type) {
case backup_setting::UI_HTML_CHECKBOX :
return new backup_setting_ui_checkbox($setting, $label, null, (array)$attributes, (array)$options);
case backup_setting::UI_HTML_DROPDOWN :
return new backup_setting_ui_select($setting, $label, null, (array)$attributes, (array)$options);
case backup_setting::UI_HTML_RADIOBUTTON :
return new backup_setting_ui_radio($setting, $label, null, null, (array)$attributes, (array)$options);
case backup_setting::UI_HTML_TEXTFIELD :
return new backup_setting_ui_text($setting, $label, $attributes, $options);
default:
throw new backup_setting_ui_exception('setting_invalid_ui_type');
}
}
/**
* Get element properties that can be used to make a quickform element
*
* @param base_task $task
* @param renderer_base $output
* @return array
*/
abstract public function get_element_properties(base_task $task = null, renderer_base $output = null);
/**
* Applies config options to a given properties array and then returns it
* @param array $properties
* @return array
*/
public function apply_options(array $properties) {
if (!empty($this->options['size'])) {
$properties['attributes']['size'] = $this->options['size'];
}
return $properties;
}
/**
* Gets the label for this item
* @param base_task $task Optional, if provided and the setting is an include
* $task is used to set the setting label
* @return string
*/
public function get_label(base_task $task = null) {
// If a task has been provided and the label is not already set meaningfully
// we will attempt to improve it.
if (!is_null($task) && $this->label == $this->setting->get_name() && strpos($this->setting->get_name(), '_include') !== false) {
if ($this->setting->get_level() == backup_setting::SECTION_LEVEL) {
$this->label = get_string('includesection', 'backup', $task->get_name());
} else if ($this->setting->get_level() == backup_setting::ACTIVITY_LEVEL) {
$this->label = $task->get_name();
}
}
return $this->label;
}
/**
* Returns true if the setting is changeable.
*
* A setting is changeable if it meets either of the two following conditions.
*
* 1. The setting is not locked
* 2. The setting is locked but only by settings that are of the same level (same page)
*
* Condition 2 is really why we have this function
* @param int $level Optional, if provided only depedency_settings below or equal to this level are considered,
* when checking if the ui_setting is changeable. Although dependencies might cause a lock on this setting,
* they could be changeable in the same view.
* @return bool
*/
public function is_changeable($level = null) {
if ($this->setting->get_status() === backup_setting::NOT_LOCKED) {
// Its not locked so its chanegable.
return true;
} else if ($this->setting->get_status() !== backup_setting::LOCKED_BY_HIERARCHY) {
// Its not changeable because its locked by permission or config.
return false;
} else if ($this->setting->has_dependencies_on_settings()) {
foreach ($this->setting->get_settings_depended_on() as $dependency) {
if ($level && $dependency->get_setting()->get_level() >= $level) {
continue;
}
if ($dependency->is_locked() && $dependency->get_setting()->get_level() !== $this->setting->get_level()) {
// Its not changeable because one or more dependancies arn't changeable.
return false;
}
}
// Its changeable because all dependencies are changeable.
return true;
}
// We should never get here but if we do return false to be safe.
// The setting would need to be locked by hierarchy and not have any deps.
return false;
}
}
/**
* A text input user interface element for backup settings
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class backup_setting_ui_text extends backup_setting_ui {
/**
* @var int
*/
protected $type = backup_setting::UI_HTML_TEXTFIELD;
/**
* Returns an array of properties suitable for generating a quickforms element
* @param base_task $task
* @param renderer_base $output
* @return array (element, name, label, attributes)
*/
public function get_element_properties(base_task $task = null, renderer_base $output = null) {
$icon = $this->get_icon();
$context = context_course::instance($task->get_courseid());
$label = format_string($this->get_label($task), true, array('context' => $context));
if (!empty($icon)) {
$label .= $output->render($icon);
}
// Name, label, attributes.
return $this->apply_options(array(
'element' => 'text',
'name' => self::NAME_PREFIX.$this->name,
'label' => $label,
'attributes' => $this->attributes)
);
}
}
/**
* A checkbox user interface element for backup settings (default)
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class backup_setting_ui_checkbox extends backup_setting_ui {
/**
* @var int
*/
protected $type = backup_setting::UI_HTML_CHECKBOX;
/**
* @var bool
*/
protected $changeable = true;
/**
* The text to show next to the checkbox
* @var string
*/
protected $text;
/**
* Overridden constructor so we can take text argument
*
* @param backup_setting $setting
* @param string $label
* @param string $text
* @param array $attributes
* @param array $options
*/
public function __construct(backup_setting $setting, $label = null, $text = null, array $attributes = array(), array $options = array()) {
parent::__construct($setting, $label, $attributes, $options);
$this->text = $text;
}
/**
* Returns an array of properties suitable for generating a quickforms element
* @param base_task $task
* @param renderer_base $output
* @return array (element, name, label, text, attributes);
*/
public function get_element_properties(base_task $task = null, renderer_base $output = null) {
// Name, label, text, attributes.
$icon = $this->get_icon();
$context = context_course::instance($task->get_courseid());
$label = format_string($this->get_label($task), true, array('context' => $context));
if (!empty($icon)) {
$label .= $output->render($icon);
}
return $this->apply_options(array(
'element' => 'checkbox',
'name' => self::NAME_PREFIX.$this->name,
'label' => $label,
'text' => $this->text,
'attributes' => $this->attributes
));
}
/**
* Sets the text for the element
* @param string $text
*/
public function set_text($text) {
$this->text = $text;
}
/**
* Gets the static value for the element
* @global core_renderer $OUTPUT
* @return string
*/
public function get_static_value() {
global $OUTPUT;
// Checkboxes are always yes or no.
if ($this->get_value()) {
return $OUTPUT->pix_icon('i/valid', get_string('yes'));
} else {
return $OUTPUT->pix_icon('i/invalid', get_string('no'));
}
}
/**
* Returns true if the setting is changeable
* @param int $level Optional, if provided only depedency_settings below or equal to this level are considered,
* when checking if the ui_setting is changeable. Although dependencies might cause a lock on this setting,
* they could be changeable in the same view.
* @return bool
*/
public function is_changeable($level = null) {
if ($this->changeable === false) {
return false;
} else {
return parent::is_changeable($level);
}
}
/**
* Sets whether the setting is changeable,
* Note dependencies can still mark this setting changeable or not
* @param bool $newvalue
*/
public function set_changeable($newvalue) {
$this->changeable = ($newvalue);
}
}
/**
* Radio button user interface element for backup settings
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class backup_setting_ui_radio extends backup_setting_ui {
/**
* @var int
*/
protected $type = backup_setting::UI_HTML_RADIOBUTTON;
/**
* The string shown next to the input
* @var string
*/
protected $text;
/**
* The value for the radio input
* @var string
*/
protected $value;
/**
* Constructor
*
* @param backup_setting $setting
* @param string $label
* @param string $text
* @param string $value
* @param array $attributes
* @param array $options
*/
public function __construct(backup_setting $setting, $label = null, $text = null, $value = null, array $attributes = array(), array $options = array()) {
parent::__construct($setting, $label, $attributes, $options);
$this->text = $text;
$this->value = (string)$value;
}
/**
* Returns an array of properties suitable for generating a quickforms element
* @param base_task $task
* @param renderer_base $output
* @return array (element, name, label, text, value, attributes)
*/
public function get_element_properties(base_task $task = null, renderer_base $output = null) {
$icon = $this->get_icon();
$context = context_course::instance($task->get_courseid());
$label = format_string($this->get_label($task), true, array('context' => $context));
if (!empty($icon)) {
$label .= $output->render($icon);
}
// Name, label, text, value, attributes.
return $this->apply_options(array(
'element' => 'radio',
'name' => self::NAME_PREFIX.$this->name,
'label' => $label,
'text' => $this->text,
'value' => $this->value,
'attributes' => $this->attributes
));
}
/**
* Sets the text next to this input
* @param text $text
*/
public function set_text($text) {
$this->text = $text;
}
/**
* Sets the value for the input
* @param string $value
*/
public function set_value($value) {
$this->value = (string)$value;
}
/**
* Gets the static value to show for the element
*/
public function get_static_value() {
return $this->value;
}
}
/**
* A select box, drop down user interface for backup settings
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class backup_setting_ui_select extends backup_setting_ui {
/**
* @var int
*/
protected $type = backup_setting::UI_HTML_DROPDOWN;
/**
* An array of options to display in the select
* @var array
*/
protected $values;
/**
* Constructor
*
* @param backup_setting $setting
* @param string $label
* @param array $values
* @param array $attributes
* @param array $options
*/
public function __construct(backup_setting $setting, $label = null, $values = null, array $attributes = array(), array $options = array()) {
parent::__construct($setting, $label, $attributes, $options);
$this->values = $values;
}
/**
* Returns an array of properties suitable for generating a quickforms element
* @param base_task $task
* @param renderer_base $output
* @return array (element, name, label, options, attributes)
*/
public function get_element_properties(base_task $task = null, renderer_base $output = null) {
$icon = $this->get_icon();
$context = context_course::instance($task->get_courseid());
$label = format_string($this->get_label($task), true, array('context' => $context));
if (!empty($icon)) {
$label .= $output->render($icon);
}
// Name, label, options, attributes.
return $this->apply_options(array(
'element' => 'select',
'name' => self::NAME_PREFIX.$this->name,
'label' => $label,
'options' => $this->values,
'attributes' => $this->attributes
));
}
/**
* Sets the options for the select box
* @param array $values Associative array of value => text options
*/
public function set_values(array $values) {
$this->values = $values;
}
/**
* Gets the static value for this select element
* @return string
*/
public function get_static_value() {
return $this->values[$this->get_value()];
}
/**
* Returns true if the setting is changeable, false otherwise
*
* @param int $level Optional, if provided only depedency_settings below or equal to this level are considered,
* when checking if the ui_setting is changeable. Although dependencies might cause a lock on this setting,
* they could be changeable in the same view.
* @return bool
*/
public function is_changeable($level = null) {
if (count($this->values) == 1) {
return false;
} else {
return parent::is_changeable($level);
}
}
/**
* Returns the list of available values
* @return array
*/
public function get_values() {
return $this->values;
}
}
/**
* A date selector user interface widget for backup settings.
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class backup_setting_ui_dateselector extends backup_setting_ui_text {
/**
* Returns an array of properties suitable for generating a quickforms element
* @param base_task $task
* @param renderer_base $output
* @return array (element, name, label, options, attributes)
*/
public function get_element_properties(base_task $task = null, renderer_base $output = null) {
if (!array_key_exists('optional', $this->attributes)) {
$this->attributes['optional'] = false;
}
$properties = parent::get_element_properties($task, $output);
$properties['element'] = 'date_selector';
return $properties;
}
/**
* Gets the static value for this select element
* @return string
*/
public function get_static_value() {
$value = $this->get_value();
if (!empty($value)) {
return userdate($value);
}
return parent::get_static_value();
}
}
/**
* A wrapper for defaultcustom form element - can have either text or date_selector type
*
* @package core_backup
* @copyright 2017 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class backup_setting_ui_defaultcustom extends backup_setting_ui_text {
/**
* Constructor
*
* @param backup_setting $setting
* @param string $label The label to display with the setting ui
* @param array $attributes Array of HTML attributes to apply to the element
* @param array $options Array of options to apply to the setting ui object
*/
public function __construct(backup_setting $setting, $label = null, array $attributes = null, array $options = null) {
if (!is_array($attributes)) {
$attributes = [];
}
$attributes += ['customlabel' => get_string('overwrite', 'backup'),
'type' => 'text'];
parent::__construct($setting, $label, $attributes, $options);
}
/**
* Returns an array of properties suitable for generating a quickforms element
* @param base_task $task
* @param renderer_base $output
* @return array (element, name, label, options, attributes)
*/
public function get_element_properties(base_task $task = null, renderer_base $output = null) {
return ['element' => 'defaultcustom'] + parent::get_element_properties($task, $output);
}
/**
* Gets the static value for this select element
* @return string
*/
public function get_static_value() {
$value = $this->get_value();
if ($value === false) {
$value = $this->attributes['defaultvalue'];
}
if (!empty($value)) {
if ($this->attributes['type'] === 'date_selector' ||
$this->attributes['type'] === 'date_time_selector') {
return userdate($value);
}
}
return $value;
}
}
/**
* Base setting UI exception class.
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class base_setting_ui_exception extends base_setting_exception {}
/**
* Backup setting UI exception class.
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class backup_setting_ui_exception extends base_setting_ui_exception {};
+651
View File
@@ -0,0 +1,651 @@
<?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/>.
/**
* Backup user interface stages
*
* This file contains the classes required to manage the stages that make up the
* backup user interface.
* These will be primarily operated a {@link backup_ui} instance.
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Abstract stage class
*
* This class should be extended by all backup stages (a requirement of many backup ui functions).
* Each stage must then define two abstract methods
* - process : To process the stage
* - initialise_stage_form : To get a backup_moodleform instance for the stage
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class backup_ui_stage extends base_ui_stage {
/**
* Constructor.
*
* @param backup_ui $ui
* @param array $params
*/
public function __construct(backup_ui $ui, array $params = null) {
parent::__construct($ui, $params);
}
/**
* The backup id from the backup controller
* @return string
*/
final public function get_backupid() {
return $this->get_uniqueid();
}
}
/**
* Class representing the initial stage of a backup.
*
* In this stage the user is required to set the root level settings.
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class backup_ui_stage_initial extends backup_ui_stage {
/**
* When set to true we skip all stages and jump to immediately processing the backup.
* @var bool
*/
protected $oneclickbackup = false;
/**
* Initial backup stage constructor
* @param backup_ui $ui
* @param array $params
*/
public function __construct(backup_ui $ui, array $params = null) {
$this->stage = backup_ui::STAGE_INITIAL;
parent::__construct($ui, $params);
}
/**
* Processes the initial backup stage
* @param base_moodleform $m
* @return int The number of changes
*/
public function process(base_moodleform $m = null) {
$form = $this->initialise_stage_form();
if ($form->is_cancelled()) {
$this->ui->cancel_process();
}
$data = $form->get_data();
if ($data && confirm_sesskey()) {
if (isset($data->oneclickbackup)) {
$this->oneclickbackup = true;
}
$tasks = $this->ui->get_tasks();
$changes = 0;
foreach ($tasks as &$task) {
// We are only interesting in the backup root task for this stage.
if ($task instanceof backup_root_task) {
// Get all settings into a var so we can iterate by reference.
$settings = $task->get_settings();
foreach ($settings as &$setting) {
$name = $setting->get_ui_name();
if (isset($data->$name) && $data->$name != $setting->get_value()) {
$setting->set_value($data->$name);
$changes++;
} else if (!isset($data->$name) && $setting->get_value() &&
$setting->get_ui_type() == backup_setting::UI_HTML_CHECKBOX &&
$setting->get_status() !== backup_setting::LOCKED_BY_HIERARCHY) {
$setting->set_value(0);
$changes++;
}
}
}
}
// Return the number of changes the user made.
return $changes;
} else {
return false;
}
}
/**
* Gets the next stage for the backup.
*
* We override this function to implement the one click backup.
* When the user performs a one click backup we jump straight to the final stage.
*
* @return int
*/
public function get_next_stage() {
if ($this->oneclickbackup) {
// Its a one click backup.
// The default filename is backup.mbz, this normally gets set to something useful in the confirmation stage.
// because we skipped that stage we must manually set this to a useful value.
$tasks = $this->ui->get_tasks();
foreach ($tasks as $task) {
if ($task instanceof backup_root_task) {
// Find the filename setting.
$setting = $task->get_setting('filename');
if ($setting) {
// Use the helper objects to get a useful name.
$filename = backup_plan_dbops::get_default_backup_filename(
$this->ui->get_format(),
$this->ui->get_type(),
$this->ui->get_controller_id(),
$this->ui->get_setting_value('users'),
$this->ui->get_setting_value('anonymize'),
false,
(bool)$this->ui->get_setting_value('files')
);
$setting->set_value($filename);
}
}
}
return backup_ui::STAGE_FINAL;
}
return parent::get_next_stage();
}
/**
* Initialises the backup_moodleform instance for this stage
*
* @return backup_initial_form
*/
protected function initialise_stage_form() {
global $PAGE;
if ($this->stageform === null) {
$form = new backup_initial_form($this, $PAGE->url);
// Store as a variable so we can iterate by reference.
$tasks = $this->ui->get_tasks();
// Iterate all tasks by reference.
$add_settings = array();
$dependencies = array();
foreach ($tasks as &$task) {
// For the initial stage we are only interested in the root settings.
if ($task instanceof backup_root_task) {
if ($this->ui instanceof import_ui) {
$form->add_heading('rootsettings', get_string('importrootsettings', 'backup'));
} else {
$form->add_heading('rootsettings', get_string('rootsettings', 'backup'));
}
$settings = $task->get_settings();
// First add all settings except the filename setting.
foreach ($settings as &$setting) {
if ($setting->get_name() == 'filename') {
continue;
}
$add_settings[] = array($setting, $task);
}
// Then add all dependencies.
foreach ($settings as &$setting) {
if ($setting->get_name() == 'filename') {
continue;
}
$dependencies[] = $setting;
}
}
}
// Add all settings at once.
$form->add_settings($add_settings);
// Add dependencies.
foreach ($dependencies as $depsetting) {
$form->add_dependencies($depsetting);
}
$this->stageform = $form;
}
// Return the form.
return $this->stageform;
}
}
/**
* Schema stage of backup process
*
* During the schema stage the user is required to set the settings that relate
* to the area that they are backing up as well as its children.
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class backup_ui_stage_schema extends backup_ui_stage {
/**
* @var int Maximum number of settings to add to form at once
*/
const MAX_SETTINGS_BATCH = 1000;
/**
* Schema stage constructor
* @param backup_ui $ui
* @param array $params
*/
public function __construct(backup_ui $ui, array $params = null) {
$this->stage = backup_ui::STAGE_SCHEMA;
parent::__construct($ui, $params);
}
/**
* Processes the schema stage
*
* @param base_moodleform $form
* @return int The number of changes the user made
*/
public function process(base_moodleform $form = null) {
$form = $this->initialise_stage_form();
// Check it wasn't cancelled.
if ($form->is_cancelled()) {
$this->ui->cancel_process();
}
// Check it has been submit.
$data = $form->get_data();
if ($data && confirm_sesskey()) {
// Get the tasks into a var so we can iterate by reference.
$tasks = $this->ui->get_tasks();
$changes = 0;
// Iterate all tasks by reference.
foreach ($tasks as &$task) {
// We are only interested in schema settings.
if (!($task instanceof backup_root_task)) {
// Store as a variable so we can iterate by reference.
$settings = $task->get_settings();
// Iterate by reference.
foreach ($settings as &$setting) {
$name = $setting->get_ui_name();
if (isset($data->$name) && $data->$name != $setting->get_value()) {
$setting->set_value($data->$name);
$changes++;
} else if (!isset($data->$name) && $setting->get_ui_type() == backup_setting::UI_HTML_CHECKBOX && $setting->get_value()) {
$setting->set_value(0);
$changes++;
}
}
}
}
// Return the number of changes the user made.
return $changes;
} else {
return false;
}
}
/**
* Creates the backup_schema_form instance for this stage
*
* @return backup_schema_form
*/
protected function initialise_stage_form() {
global $PAGE;
if ($this->stageform === null) {
$form = new backup_schema_form($this, $PAGE->url);
$tasks = $this->ui->get_tasks();
$content = '';
$courseheading = false;
$add_settings = array();
$dependencies = array();
// Track progress through each stage.
$progress = $this->ui->get_controller()->get_progress();
$progress->start_progress('Initialise stage form', 3);
// Get settings for all tasks.
$progress->start_progress('', count($tasks));
$done = 1;
foreach ($tasks as $task) {
if (!($task instanceof backup_root_task)) {
if (!$courseheading) {
// If we haven't already display a course heading to group nicely.
$form->add_heading('coursesettings', get_string('includeactivities', 'backup'));
$courseheading = true;
}
// First add each setting.
foreach ($task->get_settings() as $setting) {
$add_settings[] = array($setting, $task);
}
// The add all the dependencies.
foreach ($task->get_settings() as $setting) {
$dependencies[] = $setting;
}
} else if ($this->ui->enforce_changed_dependencies()) {
// Only show these settings if dependencies changed them.
// Add a root settings heading to group nicely.
$form->add_heading('rootsettings', get_string('rootsettings', 'backup'));
// Iterate all settings and add them to the form as a fixed
// setting. We only want schema settings to be editable.
foreach ($task->get_settings() as $setting) {
if ($setting->get_name() != 'filename') {
$form->add_fixed_setting($setting, $task);
}
}
}
// Update progress.
$progress->progress($done++);
}
$progress->end_progress();
// Add settings for tasks in batches of up to 1000. Adding settings
// in larger batches improves performance, but if it takes too long,
// we won't be able to update the progress bar so the backup might.
// time out. 1000 is chosen to balance this.
$numsettings = count($add_settings);
$progress->start_progress('', ceil($numsettings / self::MAX_SETTINGS_BATCH));
$start = 0;
$done = 1;
while ($start < $numsettings) {
$length = min(self::MAX_SETTINGS_BATCH, $numsettings - $start);
$form->add_settings(array_slice($add_settings, $start, $length));
$start += $length;
$progress->progress($done++);
}
$progress->end_progress();
$progress->start_progress('', count($dependencies));
$done = 1;
foreach ($dependencies as $depsetting) {
$form->add_dependencies($depsetting);
$progress->progress($done++);
}
$progress->end_progress();
// End overall progress through creating form.
$progress->end_progress();
$this->stageform = $form;
}
return $this->stageform;
}
}
/**
* Confirmation stage
*
* On this stage the user reviews the setting for the backup and can change the filename
* of the file that will be generated.
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class backup_ui_stage_confirmation extends backup_ui_stage {
/**
* Constructs the stage
* @param backup_ui $ui
* @param array $params
*/
public function __construct($ui, array $params = null) {
$this->stage = backup_ui::STAGE_CONFIRMATION;
parent::__construct($ui, $params);
}
/**
* Processes the confirmation stage
*
* @param base_moodleform $form
* @return int The number of changes the user made
*/
public function process(base_moodleform $form = null) {
$form = $this->initialise_stage_form();
// Check it hasn't been cancelled.
if ($form->is_cancelled()) {
$this->ui->cancel_process();
}
$data = $form->get_data();
if ($data && confirm_sesskey()) {
// Collect into a variable so we can iterate by reference.
$tasks = $this->ui->get_tasks();
$changes = 0;
// Iterate each task by reference.
foreach ($tasks as &$task) {
if ($task instanceof backup_root_task) {
// At this stage all we are interested in is the filename setting.
$setting = $task->get_setting('filename');
$name = $setting->get_ui_name();
if (isset($data->$name) && $data->$name != $setting->get_value()) {
$setting->set_value($data->$name);
$changes++;
}
}
}
// Return the number of changes the user made.
return $changes;
} else {
return false;
}
}
/**
* Creates the backup_confirmation_form instance this stage requires
*
* @return backup_confirmation_form
*/
protected function initialise_stage_form() {
global $PAGE;
if ($this->stageform === null) {
// Get the form.
$form = new backup_confirmation_form($this, $PAGE->url);
$content = '';
$courseheading = false;
foreach ($this->ui->get_tasks() as $task) {
if ($setting = $task->get_setting('filename')) {
$form->add_heading('filenamesetting', get_string('filename', 'backup'));
if ($setting->get_value() == 'backup.mbz') {
$format = $this->ui->get_format();
$type = $this->ui->get_type();
$id = $this->ui->get_controller_id();
$users = $this->ui->get_setting_value('users');
$anonymised = $this->ui->get_setting_value('anonymize');
$files = (bool)$this->ui->get_setting_value('files');
$filename = backup_plan_dbops::get_default_backup_filename(
$format,
$type,
$id,
$users,
$anonymised,
false,
$files);
$setting->set_value($filename);
}
$form->add_setting($setting, $task);
break;
}
}
// Track progress through tasks.
$progress = $this->ui->get_controller()->get_progress();
$tasks = $this->ui->get_tasks();
$progress->start_progress('initialise_stage_form', count($tasks));
$done = 1;
foreach ($tasks as $task) {
if ($task instanceof backup_root_task) {
// If its a backup root add a root settings heading to group nicely.
if ($this->ui instanceof import_ui) {
$form->add_heading('rootsettings', get_string('importrootsettings', 'backup'));
} else {
$form->add_heading('rootsettings', get_string('rootsettings', 'backup'));
}
} else if (!$courseheading) {
// We haven't already add a course heading.
$form->add_heading('coursesettings', get_string('includeditems', 'backup'));
$courseheading = true;
}
// Iterate all settings, doesnt need to happen by reference.
foreach ($task->get_settings() as $setting) {
// For this stage only the filename setting should be editable.
if ($setting->get_name() != 'filename') {
$form->add_fixed_setting($setting, $task);
}
}
// Update progress.
$progress->progress($done++);
}
$progress->end_progress();
$this->stageform = $form;
}
return $this->stageform;
}
}
/**
* Final stage of backup
*
* This stage is special in that it is does not make use of a form. The reason for
* this is the order of procession of backup at this stage.
* The processesion is:
* 1. The final stage will be intialise.
* 2. The confirmation stage will be processed.
* 3. The backup will be executed
* 4. The complete stage will be loaded by execution
* 5. The complete stage will be displayed
*
* This highlights that we neither need a form nor a display method for this stage
* we simply need to process.
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class backup_ui_stage_final extends backup_ui_stage {
/**
* Constructs the final stage
* @param backup_ui $ui
* @param array $params
*/
public function __construct(backup_ui $ui, array $params = null) {
$this->stage = backup_ui::STAGE_FINAL;
parent::__construct($ui, $params);
}
/**
* Processes the final stage.
*
* In this case it ALWAYS passes processing to the previous stage (confirmation)
*
* @param base_moodleform $form
* @return bool
*/
public function process(base_moodleform $form = null) {
return true;
}
/**
* should NEVER be called... throws an exception
*/
protected function initialise_stage_form() {
throw new backup_ui_exception('backup_ui_must_execute_first');
}
/**
* should NEVER be called... throws an exception
*
* @throws backup_ui_exception always
* @param core_backup_renderer $renderer
* @return void
*/
public function display(core_backup_renderer $renderer) {
throw new backup_ui_exception('backup_ui_must_execute_first');
}
}
/**
* The completed backup stage
*
* At this stage everything is done and the user will be redirected to view the
* backup file in the file browser.
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class backup_ui_stage_complete extends backup_ui_stage_final {
/**
* The results of the backup execution
* @var array
*/
protected $results;
/**
* Constructs the complete backup stage
*
* @param backup_ui $ui
* @param array $params
* @param array $results
*/
public function __construct(backup_ui $ui, array $params = null, array $results = null) {
$this->results = $results;
parent::__construct($ui, $params);
$this->stage = backup_ui::STAGE_COMPLETE;
}
/**
* Displays the completed backup stage.
*
* Currently this just involves redirecting to the file browser with an
* appropriate message.
*
* @param core_backup_renderer $renderer
* @return string HTML code to echo
*/
public function display(core_backup_renderer $renderer) {
// Get the resulting stored_file record.
$type = $this->get_ui()->get_controller()->get_type();
$courseid = $this->get_ui()->get_controller()->get_courseid();
switch ($type) {
case 'activity':
$cmid = $this->get_ui()->get_controller()->get_id();
$cm = get_coursemodule_from_id(null, $cmid, $courseid);
$modcontext = context_module::instance($cm->id);
$restorerul = new moodle_url('/backup/restorefile.php', array('contextid' => $modcontext->id));
break;
case 'course':
default:
$coursecontext = context_course::instance($courseid);
$restorerul = new moodle_url('/backup/restorefile.php', array('contextid' => $coursecontext->id));
}
$output = '';
$output .= $renderer->box_start();
if (!empty($this->results['include_file_references_to_external_content'])) {
$output .= $renderer->notification(get_string('filereferencesincluded', 'backup'), 'notifyproblem');
}
if (!empty($this->results['missing_files_in_pool'])) {
$output .= $renderer->notification(get_string('missingfilesinpool', 'backup'), 'notifyproblem');
}
$output .= $renderer->get_samesite_notification();
$output .= $renderer->notification(get_string('executionsuccess', 'backup'), 'notifysuccess');
$output .= $renderer->continue_button($restorerul, 'get');
$output .= $renderer->box_end();
return $output;
}
}
+435
View File
@@ -0,0 +1,435 @@
<?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/>.
/**
* This file contains the generic moodleform bridge for the backup user interface
* as well as the individual forms that relate to the different stages the user
* interface can exist within.
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/formslib.php');
/**
* Base moodleform bridge
*
* Ahhh the mighty moodleform bridge! Strong enough to take the weight of 682 full
* grown african swallows all of whom have been carring coconuts for several days.
* EWWWWW!!!!!!!!!!!!!!!!!!!!!!!!
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class base_moodleform extends moodleform {
/**
* The stage this form belongs to
* @var base_ui_stage
*/
protected $uistage = null;
/**
* True if we have a course div open, false otherwise
* @var bool
*/
protected $coursediv = false;
/**
* True if we have a section div open, false otherwise
* @var bool
*/
protected $sectiondiv = false;
/**
* True if we have an activity div open, false otherwise
* @var bool
*/
protected $activitydiv = false;
/**
* Creates the form
*
* @param base_ui_stage $uistage
* @param moodle_url|string $action
* @param mixed $customdata
* @param string $method get|post
* @param string $target
* @param array $attributes
* @param bool $editable
*/
public function __construct(base_ui_stage $uistage, $action = null, $customdata = null, $method = 'post',
$target = '', $attributes = null, $editable = true) {
$this->uistage = $uistage;
// Add a class to the attributes to prevent the default collapsible behaviour.
if (!$attributes) {
$attributes = array();
}
$attributes['class'] = 'unresponsive';
if (!isset($attributes['enctype'])) {
$attributes['enctype'] = 'application/x-www-form-urlencoded'; // Enforce compatibility with our max_input_vars hack.
}
parent::__construct($action, $customdata, $method, $target, $attributes, $editable);
}
/**
* The standard form definition... obviously not much here
*/
public function definition() {
$ui = $this->uistage->get_ui();
$mform = $this->_form;
$mform->setDisableShortforms();
$stage = $mform->addElement('hidden', 'stage', $this->uistage->get_stage());
$mform->setType('stage', PARAM_INT);
$stage = $mform->addElement('hidden', $ui->get_name(), $ui->get_uniqueid());
$mform->setType($ui->get_name(), PARAM_ALPHANUM);
$params = $this->uistage->get_params();
if (is_array($params) && count($params) > 0) {
foreach ($params as $name => $value) {
// TODO: Horrible hack, but current backup ui structure does not allow
// to make this easy (only changing params to objects that would be
// possible. MDL-38735.
$intparams = array(
'contextid', 'importid', 'target');
$stage = $mform->addElement('hidden', $name, $value);
if (in_array($name, $intparams)) {
$mform->setType($name, PARAM_INT);
} else {
// Adding setType() to avoid missing setType() warnings.
// MDL-39126: support $mform->setType() for additional backup parameters.
$mform->setType($name, PARAM_RAW);
}
}
}
}
/**
* Definition applied after the data is organised.. why's it here? because I want
* to add elements on the fly.
* @global moodle_page $PAGE
*/
public function definition_after_data() {
$buttonarray = array();
if (!$this->uistage->is_first_stage()) {
$buttonarray[] = $this->_form->createElement('submit', 'previous', get_string('previousstage', 'backup'));
} else if ($this->uistage instanceof backup_ui_stage) {
// Only display the button on the first stage of backup, they only place where it has an effect.
$buttonarray[] = $this->_form->createElement('submit', 'oneclickbackup', get_string('jumptofinalstep', 'backup'),
array('class' => 'oneclickbackup'));
}
$cancelparams = [
'data-modal' => 'confirmation',
'data-modal-content-str' => json_encode([
'confirmcancelquestion',
'backup',
]),
'data-modal-yes-button-str' => json_encode([
'yes',
'moodle',
]),
];
if ($this->uistage->get_ui() instanceof import_ui) {
$cancelparams['data-modal-title-str'] = json_encode([
'confirmcancelimport',
'backup',
]);
} else if ($this->uistage->get_ui() instanceof restore_ui) {
$cancelparams['data-modal-title-str'] = json_encode([
'confirmcancelrestore',
'backup',
]);
} else {
$cancelparams['data-modal-title-str'] = json_encode([
'confirmcancel',
'backup',
]);
}
$buttonarray[] = $this->_form->createElement('cancel', 'cancel', get_string('cancel'), $cancelparams);
$buttonarray[] = $this->_form->createElement(
'submit',
'submitbutton',
get_string($this->uistage->get_ui()->get_name().'stage'.$this->uistage->get_stage().'action', 'backup'),
array('class' => 'proceedbutton')
);
$this->_form->addGroup($buttonarray, 'buttonar', '', array(' '), false);
$this->_form->closeHeaderBefore('buttonar');
$this->_definition_finalized = true;
}
/**
* Closes any open divs
*/
public function close_task_divs() {
if ($this->activitydiv) {
$this->_form->addElement('html', html_writer::end_tag('div'));
$this->activitydiv = false;
}
if ($this->sectiondiv) {
$this->_form->addElement('html', html_writer::end_tag('div'));
$this->sectiondiv = false;
}
if ($this->coursediv) {
$this->_form->addElement('html', html_writer::end_tag('div'));
$this->coursediv = false;
}
}
/**
* Adds the backup_setting as a element to the form
* @param backup_setting $setting
* @param base_task $task
* @return bool
*/
public function add_setting(backup_setting $setting, base_task $task = null) {
return $this->add_settings(array(array($setting, $task)));
}
/**
* Adds multiple backup_settings as elements to the form
* @param array $settingstasks Consists of array($setting, $task) elements
* @return bool
*/
public function add_settings(array $settingstasks) {
global $OUTPUT;
// Determine highest setting level, which is displayed in this stage. This is relevant for considering only
// locks of dependency settings for parent settings, which are not displayed in this stage.
$highestlevel = backup_setting::ACTIVITY_LEVEL;
foreach ($settingstasks as $st) {
list($setting, $task) = $st;
if ($setting->get_level() < $highestlevel) {
$highestlevel = $setting->get_level();
}
}
$defaults = array();
foreach ($settingstasks as $st) {
list($setting, $task) = $st;
// If the setting cant be changed or isn't visible then add it as a fixed setting.
if (!$setting->get_ui()->is_changeable($highestlevel) ||
$setting->get_visibility() != backup_setting::VISIBLE) {
$this->add_fixed_setting($setting, $task);
continue;
}
// First add the formatting for this setting.
$this->add_html_formatting($setting);
// Then call the add method with the get_element_properties array.
call_user_func_array(array($this->_form, 'addElement'),
array_values($setting->get_ui()->get_element_properties($task, $OUTPUT)));
$this->_form->setType($setting->get_ui_name(), $setting->get_param_validation());
$defaults[$setting->get_ui_name()] = $setting->get_value();
if ($setting->has_help()) {
list($identifier, $component) = $setting->get_help();
$this->_form->addHelpButton($setting->get_ui_name(), $identifier, $component);
}
$this->_form->addElement('html', html_writer::end_tag('div'));
}
$this->_form->setDefaults($defaults);
return true;
}
/**
* Adds a heading to the form
* @param string $name
* @param string $text
*/
public function add_heading($name , $text) {
$this->_form->addElement('header', $name, $text);
}
/**
* Adds HTML formatting for the given backup setting, needed to group/segment
* correctly.
* @param backup_setting $setting
*/
protected function add_html_formatting(backup_setting $setting) {
$mform = $this->_form;
$isincludesetting = (strpos($setting->get_name(), '_include') !== false);
if ($isincludesetting && $setting->get_level() != backup_setting::ROOT_LEVEL) {
switch ($setting->get_level()) {
case backup_setting::COURSE_LEVEL:
if ($this->activitydiv) {
$this->_form->addElement('html', html_writer::end_tag('div'));
$this->activitydiv = false;
}
if ($this->sectiondiv) {
$this->_form->addElement('html', html_writer::end_tag('div'));
$this->sectiondiv = false;
}
if ($this->coursediv) {
$this->_form->addElement('html', html_writer::end_tag('div'));
}
$mform->addElement('html', html_writer::start_tag('div', array('class' => 'grouped_settings course_level')));
$mform->addElement('html', html_writer::start_tag('div', array('class' => 'include_setting course_level')));
$this->coursediv = true;
break;
case backup_setting::SECTION_LEVEL:
if ($this->activitydiv) {
$this->_form->addElement('html', html_writer::end_tag('div'));
$this->activitydiv = false;
}
if ($this->sectiondiv) {
$this->_form->addElement('html', html_writer::end_tag('div'));
}
$mform->addElement('html', html_writer::start_tag('div', array('class' => 'grouped_settings section_level')));
$mform->addElement('html', html_writer::start_tag('div', array('class' => 'include_setting section_level')));
$this->sectiondiv = true;
break;
case backup_setting::ACTIVITY_LEVEL:
if ($this->activitydiv) {
$this->_form->addElement('html', html_writer::end_tag('div'));
}
$mform->addElement('html', html_writer::start_tag('div', array('class' => 'grouped_settings activity_level')));
$mform->addElement('html', html_writer::start_tag('div', array('class' => 'include_setting activity_level')));
$this->activitydiv = true;
break;
default:
$mform->addElement('html', html_writer::start_tag('div', array('class' => 'normal_setting')));
break;
}
} else if ($setting->get_level() == backup_setting::ROOT_LEVEL) {
$mform->addElement('html', html_writer::start_tag('div', array('class' => 'root_setting')));
} else {
$mform->addElement('html', html_writer::start_tag('div', array('class' => 'normal_setting')));
}
}
/**
* Adds a fixed or static setting to the form
* @param backup_setting $setting
* @param base_task $task
*/
public function add_fixed_setting(backup_setting $setting, base_task $task) {
global $OUTPUT;
$settingui = $setting->get_ui();
if ($setting->get_visibility() == backup_setting::VISIBLE) {
$this->add_html_formatting($setting);
switch ($setting->get_status()) {
case backup_setting::LOCKED_BY_PERMISSION:
$icon = ' '.$OUTPUT->pix_icon('i/permissionlock', get_string('lockedbypermission', 'backup'), 'moodle',
array('class' => 'smallicon lockedicon permissionlock'));
break;
case backup_setting::LOCKED_BY_CONFIG:
$icon = ' '.$OUTPUT->pix_icon('i/configlock', get_string('lockedbyconfig', 'backup'), 'moodle',
array('class' => 'smallicon lockedicon configlock'));
break;
case backup_setting::LOCKED_BY_HIERARCHY:
$icon = ' '.$OUTPUT->pix_icon('i/hierarchylock', get_string('lockedbyhierarchy', 'backup'), 'moodle',
array('class' => 'smallicon lockedicon configlock'));
break;
default:
$icon = '';
break;
}
$context = context_course::instance($task->get_courseid());
$label = format_string($settingui->get_label($task), true, array('context' => $context));
$labelicon = $settingui->get_icon();
if (!empty($labelicon)) {
$label .= $OUTPUT->render($labelicon);
}
$this->_form->addElement('static', 'static_'.$settingui->get_name(), $label, $settingui->get_static_value().$icon);
$this->_form->addElement('html', html_writer::end_tag('div'));
}
$this->_form->addElement('hidden', $settingui->get_name(), $settingui->get_value());
$this->_form->setType($settingui->get_name(), $settingui->get_param_validation());
}
/**
* Adds dependencies to the form recursively
*
* @param backup_setting $setting
*/
public function add_dependencies(backup_setting $setting) {
$mform = $this->_form;
// Apply all dependencies for backup.
foreach ($setting->get_my_dependency_properties() as $key => $dependency) {
call_user_func_array(array($this->_form, 'disabledIf'), array_values($dependency));
}
}
/**
* Returns true if the form was cancelled, false otherwise
* @return bool
*/
public function is_cancelled() {
return (optional_param('cancel', false, PARAM_BOOL) || parent::is_cancelled());
}
/**
* Removes an element from the form if it exists
* @param string $elementname
* @return bool
*/
public function remove_element($elementname) {
if ($this->_form->elementExists($elementname)) {
return $this->_form->removeElement($elementname);
} else {
return false;
}
}
/**
* Gets an element from the form if it exists
*
* @param string $elementname
* @return HTML_QuickForm_input|MoodleQuickForm_group
*/
public function get_element($elementname) {
if ($this->_form->elementExists($elementname)) {
return $this->_form->getElement($elementname);
} else {
return false;
}
}
/**
* Displays the form
*/
public function display() {
global $PAGE, $COURSE;
$this->require_definition_after_data();
// Get list of module types on course.
$modinfo = get_fast_modinfo($COURSE);
$modnames = array_map('strval', $modinfo->get_used_module_names(true));
core_collator::asort($modnames);
$PAGE->requires->yui_module('moodle-backup-backupselectall', 'M.core_backup.backupselectall',
array($modnames));
$PAGE->requires->strings_for_js(array('select', 'all', 'none'), 'moodle');
$PAGE->requires->strings_for_js(array('showtypes', 'hidetypes'), 'backup');
parent::display();
}
/**
* Ensures the the definition after data is loaded
*/
public function require_definition_after_data() {
if (!$this->_definition_finalized) {
$this->definition_after_data();
}
}
}
+368
View File
@@ -0,0 +1,368 @@
<?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/>.
/**
* This file contains the backup user interface class
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* This is the backup user interface class
*
* The backup user interface class manages the user interface and backup for
* Moodle.
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class base_ui {
/**
* The progress of this instance of the backup ui class
* It is in the initial stage.
*/
const PROGRESS_INTIAL = 0;
/**
* The progress of this instance of the backup ui class
* It is processed.
*/
const PROGRESS_PROCESSED = 1;
/**
* The progress of this instance of the backup ui class
* It is saved.
*/
const PROGRESS_SAVED = 2;
/**
* The progress of this instance of the backup ui class
* It has been executed.
*/
const PROGRESS_EXECUTED = 3;
/**
* The controller
* @var backup_controller|restore_controller
*/
protected $controller;
/**
* The current stage
* @var base_ui_stage
*/
protected $stage;
/**
* The current progress of the UI
* @var int One of self::PROGRESS_*
*/
protected $progress;
/**
* The number of changes made by dependency enforcement
* @var int
*/
protected $dependencychanges = 0;
/**
* Yay for constructors
* @param backup_controller $controller
* @param array $params
*/
public function __construct($controller, array $params = null) {
$this->controller = $controller;
$this->progress = self::PROGRESS_INTIAL;
$this->stage = $this->initialise_stage(null, $params);
if ($this->controller) {
// Process UI event before to be safe.
$this->controller->process_ui_event();
}
}
/**
* Destorys the backup controller and the loaded stage.
*/
public function destroy() {
if ($this->controller) {
$this->controller->destroy();
}
unset($this->stage);
}
/**
* Intialises what ever stage is requested. If none are requested we check
* params for 'stage' and default to initial
*
* @param int|null $stage The desired stage to intialise or null for the default
* @param array $params
* @return base_ui_stage
*/
abstract protected function initialise_stage($stage = null, array $params = null);
/**
* This processes the current stage of the backup
* @throws backup_ui_exception
* @return bool
*/
public function process() {
if ($this->progress >= self::PROGRESS_PROCESSED) {
throw new backup_ui_exception('backupuialreadyprocessed');
}
$this->progress = self::PROGRESS_PROCESSED;
if (optional_param('previous', false, PARAM_BOOL) && $this->stage->get_stage() > $this->get_first_stage_id()) {
$this->stage = $this->initialise_stage($this->stage->get_prev_stage(), $this->stage->get_params());
return false;
}
// Process the stage.
$processoutcome = $this->stage->process();
if ($processoutcome !== false) {
$this->stage = $this->initialise_stage($this->stage->get_next_stage(), $this->stage->get_params());
}
// Process UI event after to check changes are valid.
$this->controller->process_ui_event();
return $processoutcome;
}
/**
* Saves the backup controller.
*
* Once this has been called nothing else can be changed in the controller.
*
* @throws base_ui_exception
* @return bool
*/
public function save_controller() {
if ($this->progress >= self::PROGRESS_SAVED) {
throw new base_ui_exception('backupuialreadysaved');
}
$this->progress = self::PROGRESS_SAVED;
// First enforce dependencies.
$this->enforce_dependencies();
// Process UI event after to check any changes are valid.
$this->controller->process_ui_event();
// Save the controller.
$this->controller->save_controller();
return true;
}
/**
* Displays the UI for the backup!
*
* @throws base_ui_exception
* @param core_backup_renderer $renderer
* @return string HTML code to echo
*/
public function display(core_backup_renderer $renderer) {
if ($this->progress < self::PROGRESS_SAVED) {
throw new base_ui_exception('backupsavebeforedisplay');
}
return $this->stage->display($renderer);
}
/**
* Gets all backup tasks from the controller
* @return array Array of backup_task
*/
public function get_tasks() {
$plan = $this->controller->get_plan();
$tasks = $plan->get_tasks();
return $tasks;
}
/**
* Gets the stage we are on
* @return int
*/
public function get_stage() {
return $this->stage->get_stage();
}
/**
* Gets the name of the stage we are on
* @return string
*/
public function get_stage_name() {
return $this->stage->get_name();
}
/**
* Gets the backup id from the controller
* @return string
*/
abstract public function get_uniqueid();
/**
* Executes the backup plan
* @return bool
*/
abstract public function execute();
/**
* Enforces dependencies on all settings. Call before save
* @return bool True if dependencies were enforced and changes were made
*/
protected function enforce_dependencies() {
// Get the plan.
$plan = $this->controller->get_plan();
// Get the tasks as a var so we can iterate by reference.
$tasks = $plan->get_tasks();
$changes = 0;
foreach ($tasks as &$task) {
// Store as a var so we can iterate by reference.
$settings = $task->get_settings();
foreach ($settings as &$setting) {
// Get all dependencies for iteration by reference.
$dependencies = $setting->get_dependencies();
foreach ($dependencies as &$dependency) {
// Enforce each dependency.
if ($dependency->enforce()) {
$changes++;
}
}
}
}
// Store the number of settings that changed through enforcement.
$this->dependencychanges = $changes;
return ($changes > 0);
}
/**
* Returns true if enforce_dependencies changed any settings
* @return bool
*/
public function enforce_changed_dependencies() {
return ($this->dependencychanges > 0);
}
/**
* Loads the backup controller if we are tracking one
* @throws coding_exception
* @param string|bool $uniqueid
* @return backup_controller|false
*/
public static function load_controller($uniqueid = false) {
throw new coding_exception('load_controller() method needs to be overridden in each subclass of base_ui');
}
/**
* Cancels the current backup/restore and redirects the user back to the relevant place
*/
public function cancel_process() {
global $PAGE;
// Determine the appropriate URL to redirect the user to.
if ($PAGE->context->contextlevel == CONTEXT_MODULE && $PAGE->cm !== null) {
$relevanturl = new moodle_url('/mod/'.$PAGE->cm->modname.'/view.php', array('id' => $PAGE->cm->id));
} else {
$relevanturl = new moodle_url('/course/view.php', array('id' => $PAGE->course->id));
}
redirect($relevanturl);
}
/**
* Gets an array of progress bar items that can be displayed through the backup renderer.
* @return array Array of items for the progress bar
*/
abstract public function get_progress_bar();
/**
* Gets the format for the backup
* @return int
*/
public function get_format() {
return $this->controller->get_format();
}
/**
* Gets the type of the backup
* @return int
*/
public function get_type() {
return $this->controller->get_type();
}
/**
* Returns the controller object.
* @return backup_controller|restore_controller
*/
public function get_controller() {
return $this->controller;
}
/**
* Gets the ID used in creating the controller. Relates to course/section/cm
* @return int
*/
public function get_controller_id() {
return $this->controller->get_id();
}
/**
* Gets the requested setting
* @param string $name
* @param bool $default
* @return base_setting
*/
public function get_setting($name, $default = false) {
try {
return $this->controller->get_plan()->get_setting($name);
} catch (Exception $e) {
debugging('Failed to find the setting: '.$name, DEBUG_DEVELOPER);
return $default;
}
}
/**
* Gets the value for the requested setting
*
* @param string $name
* @param bool $default
* @return mixed
*/
public function get_setting_value($name, $default = false) {
try {
return $this->controller->get_plan()->get_setting($name)->get_value();
} catch (Exception $e) {
debugging('Failed to find the setting: '.$name, DEBUG_DEVELOPER);
return $default;
}
}
/**
* Returns the name of this stage.
* @return mixed
*/
abstract public function get_name();
/**
* Returns the first stage ID.
* @return mixed
*/
abstract public function get_first_stage_id();
}
/**
* Backup user interface exception. Modelled off the backup_exception class
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class base_ui_exception extends backup_exception {}
+183
View File
@@ -0,0 +1,183 @@
<?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/>.
/**
* Backup user interface stages
*
* This file contains the classes required to manage the stages that make up the
* backup user interface.
* These will be primarily operated a {@link base_ui} instance.
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Abstract stage class
*
* This class should be extended by all backup stages (a requirement of many backup ui functions).
* Each stage must then define two abstract methods
* - process : To process the stage
* - initialise_stage_form : To get a backup_moodleform instance for the stage
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class base_ui_stage {
/**
* The current stage
* @var int
*/
protected $stage = 1;
/**
* The backuck UI object
* @var base_ui
*/
protected $ui;
/**
* The moodleform for this stage
* @var base_moodleform
*/
protected $stageform = null;
/**
* Custom form params that will be added as hidden inputs
* @var array
*/
protected $params = null;
/**
* Constructor
*
* @param base_ui $ui
* @param array $params
*/
public function __construct(base_ui $ui, array $params = null) {
$this->ui = $ui;
$this->params = $params;
}
/**
* Returns the custom params for this stage
* @return array|null
*/
final public function get_params() {
return $this->params;
}
/**
* The current stage
* @return int
*/
final public function get_stage() {
return $this->stage;
}
/**
* The next stage
* @return int
*/
public function get_next_stage() {
return floor($this->stage * 2);
}
/**
* The previous stage
* @return int
*/
final public function get_prev_stage() {
return floor($this->stage / 2);
}
/**
* The name of this stage
* @return string
*/
public function get_name() {
return get_string('currentstage' . $this->stage, 'backup');
}
/**
* The backup id from the backup controller
* @return string
*/
final public function get_uniqueid() {
return $this->ui->get_uniqueid();
}
/**
* Displays the stage.
*
* By default this involves instantiating the form for the stage and the calling
* it to display.
*
* @param core_backup_renderer $renderer
* @return string HTML code to echo
*/
public function display(core_backup_renderer $renderer) {
$form = $this->initialise_stage_form();
// A nasty hack follows to work around the sad fact that moodle quickforms
// do not allow to actually return the HTML content, just to echo it.
flush();
ob_start();
$form->display();
$output = ob_get_contents();
ob_end_clean();
return $output;
}
/**
* Processes the stage.
*
* This must be overridden by every stage as it will be different for every stage
*
* @abstract
* @param base_moodleform $form
*/
abstract public function process(base_moodleform $form = null);
/**
* Creates an instance of the correct moodleform properly populated and all
* dependencies instantiated
*
* @abstract
* @return backup_moodleform
*/
abstract protected function initialise_stage_form();
/**
* Returns the base UI class
* @return base_ui
*/
final public function get_ui() {
return $this->ui;
}
/**
* Returns true if this stage is the first stage.
* @return bool
*/
public function is_first_stage() {
return $this->stage == 1;
}
}
+170
View File
@@ -0,0 +1,170 @@
<?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/>.
/**
* Course copy class.
*
* Handles procesing data submitted by UI copy form
* and sets up the course copy process.
*
* @package core_backup
* @copyright 2020 onward The Moodle Users Association <https://moodleassociation.org/>
* @author Matt Porritt <mattp@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @deprecated since Moodle 4.1. Use copy_helper instead
*/
namespace core_backup\copy;
defined('MOODLE_INTERNAL') || die;
global $CFG;
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
/**
* Course copy class.
*
* Handles procesing data submitted by UI copy form
* and sets up the course copy process.
*
* @package core_backup
* @copyright 2020 onward The Moodle Users Association <https://moodleassociation.org/>
* @author Matt Porritt <mattp@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @deprecated since Moodle 4.1 MDL-74548 - please use copy_helper instead
* @todo MDL-75022 This class will be deleted in Moodle 4.5
* @see copy_helper
*/
class copy {
/**
* The fields required for copy operations.
*
* @var array
*/
private $copyfields = array(
'courseid', // Course id integer.
'fullname', // Fullname of the destination course.
'shortname', // Shortname of the destination course.
'category', // Category integer ID that contains the destination course.
'visible', // Integer to detrmine of the copied course will be visible.
'startdate', // Integer timestamp of the start of the destination course.
'enddate', // Integer timestamp of the end of the destination course.
'idnumber', // ID of the destination course.
'userdata', // Integer to determine if the copied course will contain user data.
);
/**
* Data required for course copy operations.
*
* @var array
*/
private $copydata = array();
/**
* List of role ids to keep enrolments for in the destination course.
*
* @var array
*/
private $roles = array();
/**
* Constructor for the class.
*
* @param \stdClass $formdata Data from the validated course copy form.
*/
public function __construct(\stdClass $formdata) {
debugging('Class \course_backup\copy\copy is deprecated. Please use the copy_helper class instead.');
$this->copydata = $this->get_copy_data($formdata);
$this->roles = $this->get_enrollment_roles($formdata);
}
/**
* Extract the enrolment roles to keep in the copied course
* from the raw submitted form data.
*
* @param \stdClass $formdata Data from the validated course copy form.
* @return array $keptroles The roles to keep.
*/
private function get_enrollment_roles(\stdClass $formdata): array {
$keptroles = array();
foreach ($formdata as $key => $value) {
if ((substr($key, 0, 5 ) === 'role_') && ($value != 0)) {
$keptroles[] = $value;
}
}
return $keptroles;
}
/**
* Take the validated form data and extract the required information for copy operations.
*
* @param \stdClass $formdata Data from the validated course copy form.
* @return \stdClass $copydata Data required for course copy operations.
* @throws \moodle_exception If one of the required copy fields is missing
*/
private function get_copy_data(\stdClass $formdata): \stdClass {
$copydata = new \stdClass();
foreach ($this->copyfields as $field) {
if (isset($formdata->{$field})) {
$copydata->{$field} = $formdata->{$field};
} else {
throw new \moodle_exception('copyfieldnotfound', 'backup', '', null, $field);
}
}
return $copydata;
}
/**
* Creates a course copy.
* Sets up relevant controllers and adhoc task.
*
* @return array $copyids THe backup and restore controller ids.
* @deprecated since Moodle 4.1 MDL-74548 - please use copy_helper instead.
* @todo MDL-75023 This method will be deleted in Moodle 4.5
* @see copy_helper::process_formdata()
* @see copy_helper::create_copy()
*/
public function create_copy(): array {
debugging('The method \core_backup\copy\copy::create_copy() is deprecated.
Please use the methods provided by copy_helper instead.', DEBUG_DEVELOPER);
$copydata = clone($this->copydata);
$copydata->keptroles = $this->roles;
return \copy_helper::create_copy($copydata);
}
/**
* Get the in progress course copy operations for a user.
*
* @param int $userid User id to get the course copies for.
* @param int $courseid The optional source course id to get copies for.
* @return array $copies Details of the inprogress copies.
* @deprecated since Moodle 4.1 MDL-74548 - please use copy_helper::get_copies() instead.
* @todo MDL-75024 This method will be deleted in Moodle 4.5
* @see copy_helper::get_copies()
*/
public static function get_copies(int $userid, int $courseid=0): array {
debugging('The method \core_backup\copy\copy::get_copies() is deprecated.
Please use copy_helper::get_copies() instead.', DEBUG_DEVELOPER);
return \copy_helper::get_copies($userid, $coursied);
}
}
@@ -0,0 +1,52 @@
<?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/>.
namespace core_backup\hook;
/**
* Get a list of event names which are excluded to trigger from course changes in automated backup.
*
* @package core_backup
* @copyright 2023 Tomo Tsuyuki <tomotsuyuki@catalyst-au.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
#[\core\attribute\label('Get a list of event names which are excluded to trigger from course changes in automated backup.')]
#[\core\attribute\tags('backup')]
final class before_course_modified_check {
/**
* @var string[] Array of event names.
*/
private $events = [];
/**
* Add an array of event names which are excluded to trigger from course changes in automated backup.
*
* @param string $events,... Array of event name strings
*/
public function exclude_events(string ...$events): void {
$this->events = array_merge($this->events, $events);
}
/**
* Get an array of event names which are excluded to trigger from course changes in automated backup.
* This is called after dispatch for the hook and use values to exclude events for backup.
*
* @return array
*/
public function get_excluded_events(): array {
return $this->events;
}
}
+239
View File
@@ -0,0 +1,239 @@
<?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/>.
/**
* Course copy form class.
*
* @package core_backup
* @copyright 2020 onward The Moodle Users Association <https://moodleassociation.org/>
* @author Matt Porritt <mattp@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_backup\output;
defined('MOODLE_INTERNAL') || die();
require_once("$CFG->libdir/formslib.php");
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
/**
* Course copy form class.
*
* @package core_backup
* @copyright 2020 onward The Moodle Users Association <https://moodleassociation.org/>
* @author Matt Porritt <mattp@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class copy_form extends \moodleform {
/**
* Build form for the course copy settings.
*
* {@inheritDoc}
* @see \moodleform::definition()
*/
public function definition() {
global $CFG, $OUTPUT, $USER;
$mform = $this->_form;
$course = $this->_customdata['course'];
$coursecontext = \context_course::instance($course->id);
$courseconfig = get_config('moodlecourse');
$returnto = $this->_customdata['returnto'];
$returnurl = $this->_customdata['returnurl'];
if (empty($course->category)) {
$course->category = $course->categoryid;
}
// Course ID.
$mform->addElement('hidden', 'courseid', $course->id);
$mform->setType('courseid', PARAM_INT);
// Return to type.
$mform->addElement('hidden', 'returnto', null);
$mform->setType('returnto', PARAM_ALPHANUM);
$mform->setConstant('returnto', $returnto);
// Notifications of current copies.
$copies = \copy_helper::get_copies($USER->id, $course->id);
if (!empty($copies)) {
$progresslink = new \moodle_url('/backup/copyprogress.php?', array('id' => $course->id));
$notificationmsg = get_string('copiesinprogress', 'backup', $progresslink->out());
$notification = $OUTPUT->notification($notificationmsg, 'notifymessage');
$mform->addElement('html', $notification);
}
// Return to URL.
$mform->addElement('hidden', 'returnurl', null);
$mform->setType('returnurl', PARAM_LOCALURL);
$mform->setConstant('returnurl', $returnurl);
// Form heading.
$mform->addElement('html', \html_writer::div(get_string('copycoursedesc', 'backup'), 'form-description mb-6'));
// Course fullname.
$mform->addElement('text', 'fullname', get_string('fullnamecourse'), 'maxlength="254" size="50"');
$mform->addHelpButton('fullname', 'fullnamecourse');
$mform->addRule('fullname', get_string('missingfullname'), 'required', null, 'client');
$mform->setType('fullname', PARAM_TEXT);
// Course shortname.
$mform->addElement('text', 'shortname', get_string('shortnamecourse'), 'maxlength="100" size="20"');
$mform->addHelpButton('shortname', 'shortnamecourse');
$mform->addRule('shortname', get_string('missingshortname'), 'required', null, 'client');
$mform->setType('shortname', PARAM_TEXT);
// Course category.
$displaylist = \core_course_category::make_categories_list(\core_course\management\helper::get_course_copy_capabilities());
if (!isset($displaylist[$course->category])) {
// Always keep current category.
$displaylist[$course->category] = \core_course_category::get($course->category, MUST_EXIST, true)->get_formatted_name();
}
$mform->addElement('autocomplete', 'category', get_string('coursecategory'), $displaylist);
$mform->addRule('category', null, 'required', null, 'client');
$mform->addHelpButton('category', 'coursecategory');
// Course visibility.
$choices = array();
$choices['0'] = get_string('hide');
$choices['1'] = get_string('show');
$mform->addElement('select', 'visible', get_string('coursevisibility'), $choices);
$mform->addHelpButton('visible', 'coursevisibility');
$mform->setDefault('visible', $courseconfig->visible);
if (!has_capability('moodle/course:visibility', $coursecontext)) {
$mform->hardFreeze('visible');
$mform->setConstant('visible', $course->visible);
}
// Course start date.
$mform->addElement('date_time_selector', 'startdate', get_string('startdate'));
$mform->addHelpButton('startdate', 'startdate');
$date = (new \DateTime())->setTimestamp(usergetmidnight(time()));
$date->modify('+1 day');
$mform->setDefault('startdate', $date->getTimestamp());
// Course enddate.
$mform->addElement('date_time_selector', 'enddate', get_string('enddate'), array('optional' => true));
$mform->addHelpButton('enddate', 'enddate');
if (!empty($CFG->enablecourserelativedates)) {
$attributes = [
'aria-describedby' => 'relativedatesmode_warning'
];
if (!empty($course->id)) {
$attributes['disabled'] = true;
}
$relativeoptions = [
0 => get_string('no'),
1 => get_string('yes'),
];
$relativedatesmodegroup = [];
$relativedatesmodegroup[] = $mform->createElement('select', 'relativedatesmode', get_string('relativedatesmode'),
$relativeoptions, $attributes);
$relativedatesmodegroup[] = $mform->createElement('html', \html_writer::span(get_string('relativedatesmode_warning'),
'', ['id' => 'relativedatesmode_warning']));
$mform->addGroup($relativedatesmodegroup, 'relativedatesmodegroup', get_string('relativedatesmode'), null, false);
$mform->addHelpButton('relativedatesmodegroup', 'relativedatesmode');
}
// Course ID number (default to the current course ID number; blank for users who can't change ID numbers).
$mform->addElement('text', 'idnumber', get_string('idnumbercourse'), 'maxlength="100" size="10"');
$mform->setDefault('idnumber', $course->idnumber);
$mform->addHelpButton('idnumber', 'idnumbercourse');
$mform->setType('idnumber', PARAM_RAW);
if (!has_capability('moodle/course:changeidnumber', $coursecontext)) {
$mform->hardFreeze('idnumber');
$mform->setConstant('idnumber', '');
}
// Keep source course user data.
$mform->addElement('select', 'userdata', get_string('userdata', 'backup'),
[0 => get_string('no'), 1 => get_string('yes')]);
$mform->setDefault('userdata', 0);
$mform->addHelpButton('userdata', 'userdata', 'backup');
$requiredcapabilities = array(
'moodle/restore:createuser', 'moodle/backup:userinfo', 'moodle/restore:userinfo'
);
if (!has_all_capabilities($requiredcapabilities, $coursecontext)) {
$mform->hardFreeze('userdata');
$mform->setConstant('userdata', 0);
}
// Keep manual enrolments.
// Only get roles actually used in this course.
$roles = role_fix_names(get_roles_used_in_context($coursecontext, false), $coursecontext);
// Only add the option if there are roles in this course.
if (!empty($roles) && has_capability('moodle/restore:createuser', $coursecontext)) {
$rolearray = array();
foreach ($roles as $role) {
$roleid = 'role_' . $role->id;
$rolearray[] = $mform->createElement('advcheckbox', $roleid,
$role->localname, '', array('group' => 2), array(0, $role->id));
}
$mform->addGroup($rolearray, 'rolearray', get_string('keptroles', 'backup'), ' ', false);
$mform->addHelpButton('rolearray', 'keptroles', 'backup');
$this->add_checkbox_controller(2);
}
$buttonarray = array();
$buttonarray[] = $mform->createElement('submit', 'submitreturn', get_string('copyreturn', 'backup'));
$buttonarray[] = $mform->createElement('submit', 'submitdisplay', get_string('copyview', 'backup'));
$buttonarray[] = $mform->createElement('cancel');
$mform->addGroup($buttonarray, 'buttonar', '', ' ', false);
}
/**
* Validation of the form.
*
* @param array $data
* @param array $files
* @return array the errors that were found
*/
public function validation($data, $files) {
global $DB;
$errors = parent::validation($data, $files);
// Add field validation check for duplicate shortname.
$courseshortname = $DB->get_record('course', array('shortname' => $data['shortname']), 'fullname', IGNORE_MULTIPLE);
if ($courseshortname) {
$errors['shortname'] = get_string('shortnametaken', '', $courseshortname->fullname);
}
// Add field validation check for duplicate idnumber.
if (!empty($data['idnumber'])) {
$courseidnumber = $DB->get_record('course', array('idnumber' => $data['idnumber']), 'fullname', IGNORE_MULTIPLE);
if ($courseidnumber) {
$errors['idnumber'] = get_string('courseidnumbertaken', 'error', $courseidnumber->fullname);
}
}
// Validate the dates (make sure end isn't greater than start).
if ($errorcode = course_validate_dates($data)) {
$errors['enddate'] = get_string($errorcode, 'error');
}
return $errors;
}
}
+366
View File
@@ -0,0 +1,366 @@
<?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/>.
/**
* Privacy Subsystem implementation for core_backup.
*
* @package core_backup
* @copyright 2018 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_backup\privacy;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\contextlist;
use core_privacy\local\request\transform;
use core_privacy\local\request\writer;
use core_privacy\local\request\userlist;
use core_privacy\local\request\approved_userlist;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
/**
* Privacy Subsystem implementation for core_backup.
*
* @copyright 2018 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
\core_privacy\local\metadata\provider,
\core_privacy\local\request\core_userlist_provider,
\core_privacy\local\request\subsystem\provider {
/**
* Return the fields which contain personal data.
*
* @param collection $items a reference to the collection to use to store the metadata.
* @return collection the updated collection of metadata items.
*/
public static function get_metadata(collection $items): collection {
$items->link_external_location(
'Backup',
[
'detailsofarchive' => 'privacy:metadata:backup:detailsofarchive'
],
'privacy:metadata:backup:externalpurpose'
);
$items->add_database_table(
'backup_controllers',
[
'operation' => 'privacy:metadata:backup_controllers:operation',
'type' => 'privacy:metadata:backup_controllers:type',
'itemid' => 'privacy:metadata:backup_controllers:itemid',
'timecreated' => 'privacy:metadata:backup_controllers:timecreated',
'timemodified' => 'privacy:metadata:backup_controllers:timemodified'
],
'privacy:metadata:backup_controllers'
);
return $items;
}
/**
* Get the list of contexts that contain user information for the specified user.
*
* @param int $userid The user to search.
* @return contextlist The contextlist containing the list of contexts used in this plugin.
*/
public static function get_contexts_for_userid(int $userid): contextlist {
$contextlist = new contextlist();
$sql = "SELECT ctx.id
FROM {backup_controllers} bc
JOIN {context} ctx
ON ctx.instanceid = bc.itemid
AND ctx.contextlevel = :contextlevel
AND bc.type = :type
WHERE bc.userid = :userid";
$params = [
'contextlevel' => CONTEXT_COURSE,
'userid' => $userid,
'type' => 'course',
];
$contextlist->add_from_sql($sql, $params);
$sql = "SELECT ctx.id
FROM {backup_controllers} bc
JOIN {course_sections} c
ON bc.itemid = c.id
AND bc.type = :type
JOIN {context} ctx
ON ctx.instanceid = c.course
AND ctx.contextlevel = :contextlevel
WHERE bc.userid = :userid";
$params = [
'contextlevel' => CONTEXT_COURSE,
'userid' => $userid,
'type' => 'section',
];
$contextlist->add_from_sql($sql, $params);
$sql = "SELECT ctx.id
FROM {backup_controllers} bc
JOIN {context} ctx
ON ctx.instanceid = bc.itemid
AND ctx.contextlevel = :contextlevel
AND bc.type = :type
WHERE bc.userid = :userid";
$params = [
'contextlevel' => CONTEXT_MODULE,
'userid' => $userid,
'type' => 'activity',
];
$contextlist->add_from_sql($sql, $params);
return $contextlist;
}
/**
* Get the list of users within a specific context.
*
* @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
*/
public static function get_users_in_context(userlist $userlist) {
$context = $userlist->get_context();
if ($context instanceof \context_course) {
$params = ['courseid' => $context->instanceid];
$sql = "SELECT bc.userid
FROM {backup_controllers} bc
WHERE bc.itemid = :courseid
AND bc.type = :typecourse";
$courseparams = ['typecourse' => 'course'] + $params;
$userlist->add_from_sql('userid', $sql, $courseparams);
$sql = "SELECT bc.userid
FROM {backup_controllers} bc
JOIN {course_sections} c
ON bc.itemid = c.id
WHERE c.course = :courseid
AND bc.type = :typesection";
$sectionparams = ['typesection' => 'section'] + $params;
$userlist->add_from_sql('userid', $sql, $sectionparams);
}
if ($context instanceof \context_module) {
$params = [
'cmid' => $context->instanceid,
'typeactivity' => 'activity'
];
$sql = "SELECT bc.userid
FROM {backup_controllers} bc
WHERE bc.itemid = :cmid
AND bc.type = :typeactivity";
$userlist->add_from_sql('userid', $sql, $params);
}
}
/**
* Export all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts to export information for.
*/
public static function export_user_data(approved_contextlist $contextlist) {
global $DB;
if (empty($contextlist->count())) {
return;
}
$user = $contextlist->get_user();
list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
$sql = "SELECT bc.*
FROM {backup_controllers} bc
JOIN {context} ctx
ON ctx.instanceid = bc.itemid AND ctx.contextlevel = :contextlevel
WHERE ctx.id {$contextsql}
AND bc.userid = :userid
ORDER BY bc.timecreated ASC";
$params = ['contextlevel' => CONTEXT_COURSE, 'userid' => $user->id] + $contextparams;
$backupcontrollers = $DB->get_recordset_sql($sql, $params);
self::recordset_loop_and_export($backupcontrollers, 'itemid', [], function($carry, $record) {
$carry[] = [
'operation' => $record->operation,
'type' => $record->type,
'itemid' => $record->itemid,
'timecreated' => transform::datetime($record->timecreated),
'timemodified' => transform::datetime($record->timemodified),
];
return $carry;
}, function($courseid, $data) {
$context = \context_course::instance($courseid);
$finaldata = (object) $data;
writer::with_context($context)->export_data([get_string('backup'), $courseid], $finaldata);
});
}
/**
* Delete all user data which matches the specified context.
* Only dealing with the specific context - not it's child contexts.
*
* @param \context $context A user context.
*/
public static function delete_data_for_all_users_in_context(\context $context) {
global $DB;
if ($context instanceof \context_course) {
$sectionsql = "itemid IN (SELECT id FROM {course_sections} WHERE course = ?) AND type = ?";
$DB->delete_records_select('backup_controllers', $sectionsql, [$context->instanceid, \backup::TYPE_1SECTION]);
$DB->delete_records('backup_controllers', ['itemid' => $context->instanceid, 'type' => \backup::TYPE_1COURSE]);
}
if ($context instanceof \context_module) {
$DB->delete_records('backup_controllers', ['itemid' => $context->instanceid, 'type' => \backup::TYPE_1ACTIVITY]);
}
return;
}
/**
* Delete multiple users within a single context.
* Only dealing with the specific context - not it's child contexts.
*
* @param approved_userlist $userlist The approved context and user information to delete information for.
*/
public static function delete_data_for_users(approved_userlist $userlist) {
global $DB;
if (empty($userlist->get_userids())) {
return;
}
$context = $userlist->get_context();
if ($context instanceof \context_course) {
list($usersql, $userparams) = $DB->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED);
$select = "itemid = :itemid AND userid {$usersql} AND type = :type";
$params = $userparams;
$params['itemid'] = $context->instanceid;
$params['type'] = \backup::TYPE_1COURSE;
$DB->delete_records_select('backup_controllers', $select, $params);
$params = $userparams;
$params['course'] = $context->instanceid;
$params['type'] = \backup::TYPE_1SECTION;
$sectionsql = "itemid IN (SELECT id FROM {course_sections} WHERE course = :course)";
$select = $sectionsql . " AND userid {$usersql} AND type = :type";
$DB->delete_records_select('backup_controllers', $select, $params);
}
if ($context instanceof \context_module) {
list($usersql, $userparams) = $DB->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED);
$select = "itemid = :itemid AND userid {$usersql} AND type = :type";
$params = $userparams;
$params['itemid'] = $context->instanceid;
$params['type'] = \backup::TYPE_1ACTIVITY;
// Delete activity backup data.
$select = "itemid = :itemid AND type = :type AND userid {$usersql}";
$params = ['itemid' => $context->instanceid, 'type' => 'activity'] + $userparams;
$DB->delete_records_select('backup_controllers', $select, $params);
}
}
/**
* Delete all user data for the specified user, in the specified contexts.
* Only dealing with the specific context - not it's child contexts.
*
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
*/
public static function delete_data_for_user(approved_contextlist $contextlist) {
global $DB;
if (empty($contextlist->count())) {
return;
}
$userid = $contextlist->get_user()->id;
foreach ($contextlist->get_contexts() as $context) {
if ($context instanceof \context_course) {
$select = "itemid = :itemid AND userid = :userid AND type = :type";
$params = [
'userid' => $userid,
'itemid' => $context->instanceid,
'type' => \backup::TYPE_1COURSE
];
$DB->delete_records_select('backup_controllers', $select, $params);
$params = [
'userid' => $userid,
'course' => $context->instanceid,
'type' => \backup::TYPE_1SECTION
];
$sectionsql = "itemid IN (SELECT id FROM {course_sections} WHERE course = :course)";
$select = $sectionsql . " AND userid = :userid AND type = :type";
$DB->delete_records_select('backup_controllers', $select, $params);
}
if ($context instanceof \context_module) {
list($usersql, $userparams) = $DB->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED);
$select = "itemid = :itemid AND userid = :userid AND type = :type";
$params = [
'itemid' => $context->instanceid,
'userid' => $userid,
'type' => \backup::TYPE_1ACTIVITY
];
$DB->delete_records_select('backup_controllers', $select, $params);
}
}
}
/**
* Loop and export from a recordset.
*
* @param \moodle_recordset $recordset The recordset.
* @param string $splitkey The record key to determine when to export.
* @param mixed $initial The initial data to reduce from.
* @param callable $reducer The function to return the dataset, receives current dataset, and the current record.
* @param callable $export The function to export the dataset, receives the last value from $splitkey and the dataset.
* @return void
*/
protected static function recordset_loop_and_export(\moodle_recordset $recordset, $splitkey, $initial,
callable $reducer, callable $export) {
$data = $initial;
$lastid = null;
foreach ($recordset as $record) {
if ($lastid && $record->{$splitkey} != $lastid) {
$export($lastid, $data);
$data = $initial;
}
$data = $reducer($data, $record);
$lastid = $record->{$splitkey};
}
$recordset->close();
if (!empty($lastid)) {
$export($lastid, $data);
}
}
}
+279
View File
@@ -0,0 +1,279 @@
<?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/>.
/**
* This file contains extension of the backup classes that override some methods
* and functionality in order to customise the backup UI for the purposes of
* import.
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Import UI class
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class import_ui extends backup_ui {
/**
* The stages of the backup user interface
* The precheck/selection stage of the backup - here you choose the initial settings.
*/
const STAGE_PRECHECK = 0;
/**
* Customises the backup progress bar
*
* @global moodle_page $PAGE
* @return array[] An array of arrays
*/
public function get_progress_bar() {
global $PAGE;
$stage = self::STAGE_COMPLETE;
$currentstage = $this->stage->get_stage();
$items = array();
while ($stage > 0) {
$classes = array('backup_stage');
if (floor($stage / 2) == $currentstage) {
$classes[] = 'backup_stage_next';
} else if ($stage == $currentstage) {
$classes[] = 'backup_stage_current';
} else if ($stage < $currentstage) {
$classes[] = 'backup_stage_complete';
}
$item = array(
'text' => strlen(decbin($stage * 2)).'. '.get_string('importcurrentstage'.$stage, 'backup'),
'class' => join(' ', $classes)
);
if ($stage < $currentstage && $currentstage < self::STAGE_COMPLETE && (!self::$skipcurrentstage || $stage * 2 != $currentstage)) {
$item['link'] = new moodle_url(
$PAGE->url,
$this->stage->get_params() + array('backup' => $this->get_backupid(), 'stage' => $stage)
);
}
array_unshift($items, $item);
$stage = floor($stage / 2);
}
$selectorlink = new moodle_url($PAGE->url, $this->stage->get_params());
$selectorlink->remove_params('importid');
$classes = ["backup_stage"];
if ($currentstage == 0) {
$classes[] = "backup_stage_current";
}
array_unshift($items, array(
'text' => '1. '.get_string('importcurrentstage0', 'backup'),
'class' => join(' ', $classes),
'link' => $selectorlink));
return $items;
}
/**
* Intialises what ever stage is requested. If none are requested we check
* params for 'stage' and default to initial
*
* @param int|null $stage The desired stage to intialise or null for the default
* @param array $params
* @return backup_ui_stage_initial|backup_ui_stage_schema|backup_ui_stage_confirmation|backup_ui_stage_final
*/
protected function initialise_stage($stage = null, array $params = null) {
if ($stage == null) {
$stage = optional_param('stage', self::STAGE_PRECHECK, PARAM_INT);
}
if (self::$skipcurrentstage) {
$stage *= 2;
}
switch ($stage) {
case backup_ui::STAGE_INITIAL:
$stage = new import_ui_stage_inital($this, $params);
break;
case backup_ui::STAGE_SCHEMA:
$stage = new import_ui_stage_schema($this, $params);
break;
case backup_ui::STAGE_CONFIRMATION:
$stage = new import_ui_stage_confirmation($this, $params);
break;
case backup_ui::STAGE_FINAL:
$stage = new import_ui_stage_final($this, $params);
break;
case self::STAGE_PRECHECK:
$stage = new import_ui_stage_precheck($this, $params);
break;
default:
$stage = false;
break;
}
return $stage;
}
}
/**
* Extends the initial stage
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class import_ui_stage_inital extends backup_ui_stage_initial {}
/**
* Class representing the precheck/selection stage of a import.
*
* In this stage the user is required to perform initial selections.
* That is a choice of which course to import from.
*
* @package core_backup
* @copyright 2019 Peter Dias
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class import_ui_stage_precheck extends backup_ui_stage {
/**
* Precheck/selection import stage constructor
* @param backup_ui $ui
* @param array $params
*/
public function __construct(backup_ui $ui, array $params = null) {
$this->stage = import_ui::STAGE_PRECHECK;
parent::__construct($ui, $params);
}
/**
* Processes the precheck/selection import stage
*
* @param base_moodleform|null $form
*/
public function process(base_moodleform $form = null) {
// Dummy functions. We don't have to do anything here.
return;
}
/**
* Gets the next stage for the import.
*
* @return int
*/
public function get_next_stage() {
return backup_ui::STAGE_INITIAL;
}
/**
* Initialises the backup_moodleform instance for this stage
*
* @return backup_moodleform|void
*/
public function initialise_stage_form() {
// Dummy functions. We don't have to do anything here.
}
}
/**
* Extends the schema stage
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class import_ui_stage_schema extends backup_ui_stage_schema {}
/**
* Extends the confirmation stage.
*
* This overides the initialise stage form to remove the filenamesetting heading
* as it is always hidden.
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class import_ui_stage_confirmation extends backup_ui_stage_confirmation {
/**
* Initialises the stages moodleform
* @return base_moodleform
*/
protected function initialise_stage_form() {
$form = parent::initialise_stage_form();
$form->remove_element('filenamesetting');
return $form;
}
/**
* Displays the stage
*
* This function is overriden so that we can manipulate the strings on the
* buttons.
*
* @param core_backup_renderer $renderer
* @return string HTML code to echo
*/
public function display(core_backup_renderer $renderer) {
$form = $this->initialise_stage_form();
$form->require_definition_after_data();
if ($e = $form->get_element('submitbutton')) {
$e->setLabel(get_string('import'.$this->get_ui()->get_name().'stage'.$this->get_stage().'action', 'backup'));
} else {
$elements = $form->get_element('buttonar')->getElements();
foreach ($elements as &$element) {
if ($element->getName() == 'submitbutton') {
$element->setValue(
get_string('import'.$this->get_ui()->get_name().'stage'.$this->get_stage().'action', 'backup')
);
}
}
}
// A nasty hack follows to work around the sad fact that moodle quickforms
// do not allow to actually return the HTML content, just to echo it.
flush();
ob_start();
$form->display();
$output = ob_get_contents();
ob_end_clean();
return $output;
}
}
/**
* Overrides the final stage.
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class import_ui_stage_final extends backup_ui_stage_final {}
/**
* Extends the restore course search to search for import courses.
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class import_course_search extends restore_course_search {
/**
* Sets up any access restrictions for the courses to be displayed in the search.
*
* This will typically call $this->require_capability().
*/
protected function setup_restrictions() {
$this->require_capability('moodle/backup:backuptargetimport');
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,78 @@
<?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/>.
/**
* This file contains the forms used by the restore stages
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* An abstract moodleform class specially designed for the restore forms.
*
* @abstract Marked abstract here because some idiot forgot to mark it abstract in code!
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class restore_moodleform extends base_moodleform {
/**
* Constructor.
*
* Overridden just for the purpose of typehinting the first arg.
*
* @param restore_ui_stage $uistage
* @param null $action
* @param null $customdata
* @param string $method
* @param string $target
* @param null $attributes
* @param bool $editable
*/
public function __construct(restore_ui_stage $uistage, $action = null, $customdata = null, $method = 'post',
$target = '', $attributes = null, $editable = true) {
parent::__construct($uistage, $action, $customdata, $method, $target, $attributes, $editable);
}
}
/**
* Restore settings form.
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class restore_settings_form extends restore_moodleform {}
/**
* Restore schema review form.
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class restore_schema_form extends restore_moodleform {}
/**
* Restore complete process review form.
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class restore_review_form extends restore_moodleform {};
+392
View File
@@ -0,0 +1,392 @@
<?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/>.
/**
* This file contains the restore user interface class
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* This is the restore user interface class
*
* The restore user interface class manages the user interface and restore for
* Moodle.
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class restore_ui extends base_ui {
/**
* The stages of the restore user interface.
* Confirm the backup you are going to restore.
*/
const STAGE_CONFIRM = 1;
/**
* The stages of the restore user interface.
* Select the destination for the restore.
*/
const STAGE_DESTINATION = 2;
/**
* The stages of the restore user interface.
* Alter the setting for the restore.
*/
const STAGE_SETTINGS = 4;
/**
* The stages of the restore user interface.
* Alter and review the schema that you are going to restore.
*/
const STAGE_SCHEMA = 8;
/**
* The stages of the restore user interface.
* The final review before the restore is run.
*/
const STAGE_REVIEW = 16;
/**
* The stages of the restore user interface.
* The restore is in process right now.
*/
const STAGE_PROCESS = 32;
/**
* The stages of the restore user interface.
* The process is complete.
*/
const STAGE_COMPLETE = 64;
/**
* The current UI stage.
* @var restore_ui_stage
*/
protected $stage = null;
/**
* @var \core\progress\base Progress indicator (where there is no controller)
*/
protected $progressreporter = null;
/**
* String mappings to the above stages
* @var array
*/
public static $stages = array(
restore_ui::STAGE_CONFIRM => 'confirm',
restore_ui::STAGE_DESTINATION => 'destination',
restore_ui::STAGE_SETTINGS => 'settings',
restore_ui::STAGE_SCHEMA => 'schema',
restore_ui::STAGE_REVIEW => 'review',
restore_ui::STAGE_PROCESS => 'process',
restore_ui::STAGE_COMPLETE => 'complete'
);
/**
* Intialises what ever stage is requested. If none are requested we check
* params for 'stage' and default to initial
*
* @throws restore_ui_exception for an invalid stage
* @param int|null $stage The desired stage to intialise or null for the default
* @param array $params
* @return restore_ui_stage_initial|restore_ui_stage_schema|restore_ui_stage_confirmation|restore_ui_stage_final
*/
protected function initialise_stage($stage = null, array $params = null) {
if ($stage == null) {
$stage = optional_param('stage', self::STAGE_CONFIRM, PARAM_INT);
}
$class = 'restore_ui_stage_'.self::$stages[$stage];
if (!class_exists($class)) {
throw new restore_ui_exception('unknownuistage');
}
$stage = new $class($this, $params);
return $stage;
}
/**
* This processes the current stage of the restore
* @throws restore_ui_exception if the progress is wrong.
* @return bool
*/
public function process() {
if ($this->progress >= self::PROGRESS_PROCESSED) {
throw new restore_ui_exception('restoreuialreadyprocessed');
}
$this->progress = self::PROGRESS_PROCESSED;
if (optional_param('previous', false, PARAM_BOOL) && $this->stage->get_stage() > self::STAGE_CONFIRM) {
$this->stage = $this->initialise_stage($this->stage->get_prev_stage(), $this->stage->get_params());
return false;
}
// Process the stage.
$processoutcome = $this->stage->process();
if ($processoutcome !== false && !($this->get_stage() == self::STAGE_PROCESS && optional_param('substage', false, PARAM_BOOL))) {
$this->stage = $this->initialise_stage($this->stage->get_next_stage(), $this->stage->get_params());
}
// Process UI event after to check changes are valid.
$this->controller->process_ui_event();
return $processoutcome;
}
/**
* Returns true if the stage is independent (not requiring a restore controller)
* @return bool
*/
public function is_independent() {
return false;
}
/**
* Gets the unique ID associated with this UI
* @return string
*/
public function get_uniqueid() {
return $this->get_restoreid();
}
/**
* Gets the restore id from the controller
* @return string
*/
public function get_restoreid() {
return $this->controller->get_restoreid();
}
/**
* Gets the progress reporter object in use for this restore UI.
*
* IMPORTANT: This progress reporter is used only for UI progress that is
* outside the restore controller. The restore controller has its own
* progress reporter which is used for progress during the main restore.
* Use the restore controller's progress reporter to report progress during
* a restore operation, not this one.
*
* This extra reporter is necessary because on some restore UI screens,
* there are long-running tasks even though there is no restore controller
* in use.
*
* @return \core\progress\none
*/
public function get_progress_reporter() {
if (!$this->progressreporter) {
$this->progressreporter = new \core\progress\none();
}
return $this->progressreporter;
}
/**
* Sets the progress reporter that will be returned by get_progress_reporter.
*
* @param \core\progress\base $progressreporter Progress reporter
*/
public function set_progress_reporter(\core\progress\base $progressreporter) {
$this->progressreporter = $progressreporter;
}
/**
* Executes the restore plan
* @throws restore_ui_exception if the progress or stage is wrong.
* @return bool
*/
public function execute() {
if ($this->progress >= self::PROGRESS_EXECUTED) {
throw new restore_ui_exception('restoreuialreadyexecuted');
}
if ($this->stage->get_stage() < self::STAGE_PROCESS) {
throw new restore_ui_exception('restoreuifinalisedbeforeexecute');
}
$this->controller->execute_plan();
$this->progress = self::PROGRESS_EXECUTED;
$this->stage = new restore_ui_stage_complete($this, $this->stage->get_params(), $this->controller->get_results());
return true;
}
/**
* Delete course which is created by restore process
*/
public function cleanup() {
global $DB;
$courseid = $this->controller->get_courseid();
if ($this->is_temporary_course_created($courseid) && $course = $DB->get_record('course', array('id' => $courseid))) {
$course->deletesource = 'restore';
delete_course($course, false);
}
}
/**
* Checks if the course is not restored fully and current controller has created it.
* @param int $courseid id of the course which needs to be checked
* @return bool
*/
protected function is_temporary_course_created($courseid) {
global $DB;
// Check if current controller instance has created new course.
if ($this->controller->get_target() == backup::TARGET_NEW_COURSE) {
$results = $DB->record_exists_sql("SELECT bc.itemid
FROM {backup_controllers} bc, {course} c
WHERE bc.operation = 'restore'
AND bc.type = 'course'
AND bc.itemid = c.id
AND bc.itemid = ?",
array($courseid)
);
return $results;
}
return false;
}
/**
* Returns true if enforce_dependencies changed any settings
* @return bool
*/
public function enforce_changed_dependencies() {
return ($this->dependencychanges > 0);
}
/**
* Loads the restore controller if we are tracking one
* @param string|bool $restoreid
* @return string
*/
final public static function load_controller($restoreid = false) {
// Get the restore id optional param.
if ($restoreid) {
try {
// Try to load the controller with it.
// If it fails at this point it is likely because this is the first load.
$controller = restore_controller::load_controller($restoreid);
return $controller;
} catch (Exception $e) {
return false;
}
}
return $restoreid;
}
/**
* Initialised the requested independent stage
*
* @throws restore_ui_exception
* @param int $stage One of self::STAGE_*
* @param int $contextid
* @return restore_ui_stage_confirm|restore_ui_stage_destination
*/
final public static function engage_independent_stage($stage, $contextid) {
if (!($stage & self::STAGE_CONFIRM + self::STAGE_DESTINATION)) {
throw new restore_ui_exception('dependentstagerequested');
}
$class = 'restore_ui_stage_'.self::$stages[$stage];
if (!class_exists($class)) {
throw new restore_ui_exception('unknownuistage');
}
return new $class($contextid);
}
/**
* Cancels the current restore and redirects the user back to the relevant place
*/
public function cancel_process() {
// Delete temporary restore course if exists.
if ($this->controller->get_target() == backup::TARGET_NEW_COURSE) {
$this->cleanup();
}
parent::cancel_process();
}
/**
* Gets an array of progress bar items that can be displayed through the restore renderer.
* @return array Array of items for the progress bar
*/
public function get_progress_bar() {
global $PAGE;
$stage = self::STAGE_COMPLETE;
$currentstage = $this->stage->get_stage();
$items = array();
while ($stage > 0) {
$classes = array('backup_stage');
if (floor($stage / 2) == $currentstage) {
$classes[] = 'backup_stage_next';
} else if ($stage == $currentstage) {
$classes[] = 'backup_stage_current';
} else if ($stage < $currentstage) {
$classes[] = 'backup_stage_complete';
}
$item = array('text' => strlen(decbin($stage)).'. '.get_string('restorestage'.$stage, 'backup'), 'class' => join(' ', $classes));
if ($stage < $currentstage && $currentstage < self::STAGE_COMPLETE && $stage > self::STAGE_DESTINATION) {
$item['link'] = new moodle_url($PAGE->url, array('restore' => $this->get_restoreid(), 'stage' => $stage));
}
array_unshift($items, $item);
$stage = floor($stage / 2);
}
return $items;
}
/**
* Gets the name of this UI
* @return string
*/
public function get_name() {
return 'restore';
}
/**
* Gets the first stage for this UI
* @return int STAGE_CONFIRM
*/
public function get_first_stage_id() {
return self::STAGE_CONFIRM;
}
/**
* Returns true if this stage has substages of which at least one needs to be displayed
* @return bool
*/
public function requires_substage() {
return ($this->stage->has_sub_stages() && !$this->stage->process());
}
/**
* Displays this stage
*
* @throws base_ui_exception if the progress is wrong.
* @param core_backup_renderer $renderer
* @return string HTML code to echo
*/
public function display(core_backup_renderer $renderer) {
if ($this->progress < self::PROGRESS_SAVED) {
throw new base_ui_exception('backupsavebeforedisplay');
}
return $this->stage->display($renderer);
}
}
/**
* Restore user interface exception. Modelled off the restore_exception class
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class restore_ui_exception extends base_ui_exception {}
+443
View File
@@ -0,0 +1,443 @@
<?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/>.
/**
* This file contains components used by the restore UI
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* A base class that can be used to build a specific search upon
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class restore_search_base implements renderable {
/**
* The default values for this components params
*/
const DEFAULT_SEARCH = '';
/**
* The param used to convey the current search string
* @var string
*/
static $VAR_SEARCH = 'search';
/**
* The current search string
* @var string|null
*/
private $search = null;
/**
* The URL for this page including required params to return to it
* @var moodle_url
*/
private $url = null;
/**
* The results of the search
* @var array|null
*/
private $results = null;
/**
* The total number of results available
* @var int
*/
private $totalcount = null;
/**
* Array of capabilities required for each item in the search
* @var array
*/
private $requiredcapabilities = array();
/**
* Max number of courses to return in a search.
* @var int
*/
private $maxresults = null;
/**
* Indicates if we have more than maxresults found.
* @var boolean
*/
private $hasmoreresults = false;
/**
* Constructor
* @param array $config Config options
*/
public function __construct(array $config = array()) {
$this->search = optional_param($this->get_varsearch(), self::DEFAULT_SEARCH, PARAM_NOTAGS);
$this->search = trim($this->search);
$this->maxresults = get_config('backup', 'import_general_maxresults');
foreach ($config as $name => $value) {
$method = 'set_'.$name;
if (method_exists($this, $method)) {
$this->$method($value);
}
}
}
/**
* The URL for this search
* @global moodle_page $PAGE
* @return moodle_url The URL for this page
*/
final public function get_url() {
global $PAGE;
$params = array(
$this->get_varsearch() => $this->get_search()
);
return ($this->url !== null) ? new moodle_url($this->url, $params) : new moodle_url($PAGE->url, $params);
}
/**
* The current search string
* @return string
*/
final public function get_search() {
return ($this->search !== null) ? $this->search : self::DEFAULT_SEARCH;
}
/**
* The total number of results
* @return int
*/
final public function get_count() {
if ($this->totalcount === null) {
$this->search();
}
return $this->totalcount;
}
/**
* Returns an array of results from the search
* @return array
*/
final public function get_results() {
if ($this->results === null) {
$this->search();
}
return $this->results;
}
/**
* Sets the page URL
* @param moodle_url $url
*/
final public function set_url(moodle_url $url) {
$this->url = $url;
}
/**
* Invalidates the results collected so far
*/
final public function invalidate_results() {
$this->results = null;
$this->totalcount = null;
}
/**
* Adds a required capability which all results will be checked against
* @param string $capability
* @param int|null $user
*/
final public function require_capability($capability, $user = null) {
if (!is_int($user)) {
$user = null;
}
$this->requiredcapabilities[] = array(
'capability' => $capability,
'user' => $user
);
}
/**
* Executes the search
*
* @global moodle_database $DB
* @return int The number of results
*/
final public function search() {
global $DB;
if (!is_null($this->results)) {
return $this->results;
}
$this->results = array();
$this->totalcount = 0;
$contextlevel = $this->get_itemcontextlevel();
list($sql, $params) = $this->get_searchsql();
// Get total number, to avoid some incorrect iterations.
$countsql = preg_replace('/ORDER BY.*/', '', $sql);
$totalcourses = $DB->count_records_sql("SELECT COUNT(*) FROM ($countsql) sel", $params);
if ($totalcourses > 0) {
// User to be checked is always the same (usually null, get it from first element).
$firstcap = reset($this->requiredcapabilities);
$userid = isset($firstcap['user']) ? $firstcap['user'] : null;
// Extract caps to check, this saves us a bunch of iterations.
$requiredcaps = array();
foreach ($this->requiredcapabilities as $cap) {
$requiredcaps[] = $cap['capability'];
}
// Iterate while we have records and haven't reached $this->maxresults.
$resultset = $DB->get_recordset_sql($sql, $params);
foreach ($resultset as $result) {
context_helper::preload_from_record($result);
$classname = context_helper::get_class_for_level($contextlevel);
$context = $classname::instance($result->id);
if (count($requiredcaps) > 0) {
if (!has_all_capabilities($requiredcaps, $context, $userid)) {
continue;
}
}
// Check if we are over the limit.
if ($this->totalcount + 1 > $this->maxresults) {
$this->hasmoreresults = true;
break;
}
// If not, then continue.
$this->totalcount++;
$this->results[$result->id] = $result;
}
$resultset->close();
}
return $this->totalcount;
}
/**
* Returns true if there are more search results.
* @return bool
*/
final public function has_more_results() {
if ($this->results === null) {
$this->search();
}
return $this->hasmoreresults;
}
/**
* Returns an array containing the SQL for the search and the params
* @return array
*/
abstract protected function get_searchsql();
/**
* Gets the context level associated with this components items
* @return CONTEXT_*
*/
abstract protected function get_itemcontextlevel();
/**
* Formats the results
*/
abstract protected function format_results();
/**
* Gets the string used to transfer the search string for this compontents requests
* @return string
*/
abstract public function get_varsearch();
}
/**
* A course search component
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class restore_course_search extends restore_search_base {
/**
* @var string
*/
static $VAR_SEARCH = 'search';
/**
* The current course id.
* @var int
*/
protected $currentcourseid = null;
/**
* Determines if the current course is included in the results.
* @var bool
*/
protected $includecurrentcourse;
/**
* Constructor
* @param array $config
* @param int $currentcouseid The current course id so it can be ignored
*/
public function __construct(array $config = array(), $currentcouseid = null) {
parent::__construct($config);
$this->setup_restrictions();
$this->currentcourseid = $currentcouseid;
$this->includecurrentcourse = false;
}
/**
* Sets up any access restrictions for the courses to be displayed in the search.
*
* This will typically call $this->require_capability().
*/
protected function setup_restrictions() {
$this->require_capability('moodle/restore:restorecourse');
}
/**
* Get the search SQL.
* @global moodle_database $DB
* @return array
*/
protected function get_searchsql() {
global $DB;
$ctxselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
$ctxjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)";
$params = array(
'contextlevel' => CONTEXT_COURSE,
'fullnamesearch' => '%'.$this->get_search().'%',
'shortnamesearch' => '%'.$this->get_search().'%'
);
$select = " SELECT c.id, c.fullname, c.shortname, c.visible, c.sortorder ";
$from = " FROM {course} c ";
$where = " WHERE (".$DB->sql_like('c.fullname', ':fullnamesearch', false)." OR ".
$DB->sql_like('c.shortname', ':shortnamesearch', false).")";
$orderby = " ORDER BY c.sortorder";
if ($this->currentcourseid !== null && !$this->includecurrentcourse) {
$where .= " AND c.id <> :currentcourseid";
$params['currentcourseid'] = $this->currentcourseid;
}
return array($select.$ctxselect.$from.$ctxjoin.$where.$orderby, $params);
}
/**
* Gets the context level for the search result items.
* @return CONTEXT_|int
*/
protected function get_itemcontextlevel() {
return CONTEXT_COURSE;
}
/**
* Formats results.
*/
protected function format_results() {}
/**
* Returns the name the search variable should use
* @return string
*/
public function get_varsearch() {
return self::$VAR_SEARCH;
}
/**
* Returns true if the current course should be included in the results.
*/
public function set_include_currentcourse() {
$this->includecurrentcourse = true;
}
/**
* Get the current course id
*
* @return int
*/
public function get_current_course_id(): int {
return $this->currentcourseid;
}
}
/**
* A category search component
*
* @package core_backup
* @copyright 2010 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class restore_category_search extends restore_search_base {
/**
* The search variable to use.
* @var string
*/
static $VAR_SEARCH = 'catsearch';
/**
* Constructor
* @param array $config
*/
public function __construct(array $config = array()) {
parent::__construct($config);
$this->require_capability('moodle/course:create');
}
/**
* Returns the search SQL.
* @global moodle_database $DB
* @return array
*/
protected function get_searchsql() {
global $DB;
$ctxselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
$ctxjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)";
$params = array(
'contextlevel' => CONTEXT_COURSECAT,
'namesearch' => '%'.$this->get_search().'%',
);
$select = " SELECT c.id, c.name, c.visible, c.sortorder, c.description, c.descriptionformat ";
$from = " FROM {course_categories} c ";
$where = " WHERE ".$DB->sql_like('c.name', ':namesearch', false);
$orderby = " ORDER BY c.sortorder";
return array($select.$ctxselect.$from.$ctxjoin.$where.$orderby, $params);
}
/**
* Returns the context level of the search results.
* @return CONTEXT_COURSECAT
*/
protected function get_itemcontextlevel() {
return CONTEXT_COURSECAT;
}
/**
* Formats the results.
*/
protected function format_results() {}
/**
* Returns the name to use for the search variable.
* @return string
*/
public function get_varsearch() {
return self::$VAR_SEARCH;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,76 @@
<?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/>.
namespace core_backup;
use base_setting;
use base_setting_ui;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot.'/backup/util/settings/tests/settings_test.php');
/**
* Tests for base_setting_ui class.
*
* @package core_backup
* @copyright 2021 Université Rennes 2 {@link https://www.univ-rennes2.fr}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class base_setting_ui_test extends \advanced_testcase {
/**
* Tests set_label().
*
* @return void
*/
public function test_set_label(): void {
$this->resetAfterTest();
$bs = new mock_base_setting('test', base_setting::IS_BOOLEAN);
$bsui = new base_setting_ui($bs);
// Should keep original text string.
$bsui->set_label('Section name');
$this->assertEquals('Section name', $bsui->get_label());
// Should keep original HTML string.
$bsui->set_label('<b>Section name</b>');
$this->assertEquals('<b>Section name</b>', $bsui->get_label());
// Should be converted to text string.
$bsui->set_label(123);
$this->assertSame('123', $bsui->get_label());
// Should be converted to non-breaking space (U+00A0) when label is empty.
$bsui->set_label('');
$this->assertSame("\u{00A0}", $bsui->get_label());
// Should be converted to non-breaking space (U+00A0) when the trimmed label is empty.
$bsui->set_label(" \t\t\n\n\t\t ");
$this->assertSame("\u{00A0}", $bsui->get_label());
// Should clean partially the wrong bits.
$bsui->set_label('<b onmouseover=alert("test")>label</b>');
$this->assertSame('<b>label</b>', $bsui->get_label());
// Should raise an exception when cleaning ends with 100% empty.
$this->expectException(\Exception::class);
$this->expectExceptionMessage('error/setting_invalid_ui_label');
$bsui->set_label('<script>alert("test")</script>');
}
}
@@ -0,0 +1,66 @@
@core @core_backup
Feature: Backup Moodle courses
In order to save and store course contents
As an admin
I need to create backups of courses
Background:
Given the following "courses" exist:
| fullname | shortname | category | numsections | initsections |
| Course 1 | C1 | 0 | 10 | 1 |
| Course 2 | C2 | 0 | 2 | 1 |
And the following "activities" exist:
| activity | course | idnumber | name | intro | section |
| assign | C2 | assign1 | Test assign | Assign description | 1 |
| data | C2 | data1 | Test data | Database description | 2 |
And the following config values are set as admin:
| enableasyncbackup | 0 |
And I log in as "admin"
Scenario: Backup a course providing options
When I backup "Course 1" course using this options:
| Confirmation | Filename | test_backup.mbz |
Then I should see "Restore"
And I click on "Restore" "link" in the "test_backup.mbz" "table_row"
And I should see "URL of backup"
And I should see "Anonymize user information"
@javascript
Scenario: Backup a course with default options
When I backup "Course 1" course using this options:
| Initial | Include calendar events | 0 |
| Initial | Include course logs | 1 |
| Schema | Section 5 | 0 |
| Confirmation | Filename | test_backup.mbz |
Then I should see "Restore"
And I click on "Restore" "link" in the "test_backup.mbz" "table_row"
And I should not see "Section 5" in the "region-main" "region"
And I press "Continue"
And I click on "Continue" "button" in the ".bcs-current-course" "css_element"
And "No" "icon" should exist in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' fitem ')][contains(., 'Include calendar events')]" "xpath_element"
And "Include course logs" "checkbox" should exist
And I press "Next"
Scenario: Backup a course without blocks
When I backup "Course 1" course using this options:
| 1 | setting_root_blocks | 0 |
Then I should see "Course backup area"
Scenario: Backup selecting just one section
When I backup "Course 2" course using this options:
| Schema | Test data | 0 |
| Schema | Section 2 | 0 |
| Confirmation | Filename | test_backup.mbz |
Then I should see "Course backup area"
And I click on "Restore" "link" in the "test_backup.mbz" "table_row"
And I should not see "Section 2" in the "region-main" "region"
And I press "Continue"
And I click on "Continue" "button" in the ".bcs-current-course" "css_element"
And I press "Next"
And I should see "Test assign"
And I should not see "Test data"
Scenario: Backup a course using the one click backup button
When I perform a quick backup of course "Course 2"
Then I should see "Course backup area"
And I should see "backup-moodle2-course-"
@@ -0,0 +1,105 @@
@core @core_backup @core_h5p @mod_h5pactivity @_switch_iframe @javascript
Feature: Backup xAPI states
In order to save and restore xAPI states
As an admin
I need to create backups with xAPI states and restore them
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@example.com |
And the following "course" exists:
| fullname | Course 1 |
| shortname | C1 |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
And the following "activity" exists:
| activity | h5pactivity |
| course | C1 |
| name | Awesome H5P package |
| packagefilepath | h5p/tests/fixtures/filltheblanks.h5p |
And the following config values are set as admin:
| enableasyncbackup | 0 |
# Save state for the student user.
And I am on the "Awesome H5P package" "h5pactivity activity" page logged in as student1
And I switch to "h5p-player" class iframe
And I switch to "h5p-iframe" class iframe
And I set the field with xpath "//input[contains(@aria-label,\"Blank input 1 of 4\")]" to "Narnia"
And I switch to the main frame
And I am on the "Course 1" course page
And I am on the "Awesome H5P package" "h5pactivity activity" page
And I switch to "h5p-player" class iframe
And I switch to "h5p-iframe" class iframe
And the field with xpath "//input[contains(@aria-label,\"Blank input 1 of 4\")]" matches value "Narnia"
And I log out
Scenario: Content state is backup/restored when user data is included
# Backup and restore the course.
Given I log in as "admin"
And I backup "Course 1" course using this options:
| Confirmation | Filename | test_backup.mbz |
And I restore "test_backup.mbz" backup into a new course using this options:
| Settings | Include enrolled users | 1 |
| Schema | User data | 1 |
| Schema | Course name | Course 2 |
| Schema | Course short name | C2 |
# Login as student and confirm xAPI state has been restored.
When I am on the "Course 2" course page logged in as student1
And I click on "Awesome H5P package" "link" in the "region-main" "region"
And I switch to "h5p-player" class iframe
And I switch to "h5p-iframe" class iframe
Then the field with xpath "//input[contains(@aria-label,\"Blank input 1 of 4\")]" matches value "Narnia"
Scenario: Content state is not restored when user data is not included in the backup
# Backup course without user data and then restore it.
When I log in as "admin"
And I backup "Course 1" course using this options:
| Initial | Include enrolled users | 0 |
| Confirmation | Filename | test_backup.mbz |
And I restore "test_backup.mbz" backup into a new course using this options:
| Schema | Course name | Course 2 |
| Schema | Course short name | C2 |
# Enrol student to the new course.
And the following "course enrolments" exist:
| user | course | role |
| student1 | C2 | student |
# Login as student and confirm xAPI state hasn't been restored.
And I am on the "Course 2" course page logged in as student1
And I click on "Awesome H5P package" "link" in the "region-main" "region"
And I switch to "h5p-player" class iframe
And I switch to "h5p-iframe" class iframe
Then the field with xpath "//input[contains(@aria-label,\"Blank input 1 of 4\")]" does not match value "Narnia"
Scenario: Content state is not restored when user data is included in the backup but xAPI state is not restored
# Backup with user data and restore it without user data the course.
Given I log in as "admin"
And I backup "Course 1" course using this options:
| Confirmation | Filename | test_backup.mbz |
And I restore "test_backup.mbz" backup into a new course using this options:
| Settings | Include user's state in content such as H5P activities | 0 |
| Schema | Course name | Course 2 |
| Schema | Course short name | C2 |
# Login as student and confirm xAPI state hasn't been restored.
When I am on the "Course 2" course page logged in as student1
And I click on "Awesome H5P package" "link" in the "region-main" "region"
And I switch to "h5p-player" class iframe
And I switch to "h5p-iframe" class iframe
Then the field with xpath "//input[contains(@aria-label,\"Blank input 1 of 4\")]" does not match value "Narnia"
Scenario: Content state is not restored when it is not included explicitly in the backup
# Backup course with user data but without xAPI state and then restore it.
When I log in as "admin"
And I backup "Course 1" course using this options:
| Initial | Include user's state in content such as H5P activities | 0 |
| Confirmation | Filename | test_backup.mbz |
And I restore "test_backup.mbz" backup into a new course using this options:
| Schema | Course name | Course 2 |
| Schema | Course short name | C2 |
And I should see "Awesome H5P package"
# Login as student and confirm xAPI state hasn't been restored.
And I am on the "Course 2" course page logged in as student1
And I click on "Awesome H5P package" "link" in the "region-main" "region"
And I switch to "h5p-player" class iframe
And I switch to "h5p-iframe" class iframe
Then the field with xpath "//input[contains(@aria-label,\"Blank input 1 of 4\")]" does not match value "Narnia"
+382
View File
@@ -0,0 +1,382 @@
<?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/>.
/**
* Backup and restore actions to help behat feature files writting.
*
* @package core_backup
* @category test
* @copyright 2013 David Monllaó
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
require_once(__DIR__ . '/../../../../../lib/behat/behat_base.php');
require_once(__DIR__ . '/../../../../../lib/behat/behat_field_manager.php');
require_once(__DIR__ . '/../../../../../lib/tests/behat/behat_navigation.php');
require_once(__DIR__ . '/../../../../../lib/behat/form_field/behat_form_field.php');
use Behat\Gherkin\Node\TableNode as TableNode,
Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException,
Behat\Mink\Exception\ExpectationException as ExpectationException;
/**
* Backup-related steps definitions.
*
* @package core_backup
* @category test
* @copyright 2013 David Monllaó
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_backup extends behat_base {
/**
* Backups the specified course using the provided options. If you are interested in restoring this backup would be
* useful to provide a 'Filename' option.
*
* @Given /^I backup "(?P<course_fullname_string>(?:[^"]|\\")*)" course using this options:$/
* @param string $backupcourse
* @param TableNode $options Backup options or false if no options provided
*/
public function i_backup_course_using_this_options($backupcourse, $options = false) {
// We can not use other steps here as we don't know where the provided data
// table elements are used, and we need to catch exceptions contantly.
// Navigate to the course backup page.
$this->execute("behat_navigation::i_am_on_page_instance", [$backupcourse, 'backup']);
// Initial settings.
$this->fill_backup_restore_form($this->get_step_options($options, "Initial"));
$this->execute("behat_forms::press_button", get_string('backupstage1action', 'backup'));
// Schema settings.
$this->fill_backup_restore_form($this->get_step_options($options, "Schema"));
$this->execute("behat_forms::press_button", get_string('backupstage2action', 'backup'));
// Confirmation and review, backup filename can also be specified.
$this->fill_backup_restore_form($this->get_step_options($options, "Confirmation"));
$this->execute("behat_forms::press_button", get_string('backupstage4action', 'backup'));
// Waiting for it to finish.
$this->execute("behat_general::wait_until_the_page_is_ready");
// Last backup continue button.
$this->execute("behat_general::i_click_on", array(get_string('backupstage16action', 'backup'), 'button'));
}
/**
* Performs a quick (one click) backup of a course.
*
* Please note that because you can't set settings with this there is no way to know what the filename
* that was produced was. It contains a timestamp making it hard to find.
*
* @Given /^I perform a quick backup of course "(?P<course_fullname_string>(?:[^"]|\\")*)"$/
* @param string $backupcourse
*/
public function i_perform_a_quick_backup_of_course($backupcourse) {
// We can not use other steps here as we don't know where the provided data
// table elements are used, and we need to catch exceptions contantly.
// Navigate to the course backup page.
$this->execute("behat_navigation::i_am_on_page_instance", [$backupcourse, 'backup']);
// Initial settings.
$this->execute("behat_forms::press_button", get_string('jumptofinalstep', 'backup'));
// Waiting for it to finish.
$this->execute("behat_general::wait_until_the_page_is_ready");
// Last backup continue button.
$this->execute("behat_general::i_click_on", array(get_string('backupstage16action', 'backup'), 'button'));
}
/**
* Imports the specified origin course into the other course using the provided options.
*
* Keeping it separatelly from backup & restore, it the number of
* steps and duplicate code becomes bigger a common method should
* be generalized.
*
* @Given /^I import "(?P<from_course_fullname_string>(?:[^"]|\\")*)" course into "(?P<to_course_fullname_string>(?:[^"]|\\")*)" course using this options:$/
* @param string $fromcourse
* @param string $tocourse
* @param TableNode $options
*/
public function i_import_course_into_course($fromcourse, $tocourse, $options = false) {
// We can not use other steps here as we don't know where the provided data
// table elements are used, and we need to catch exceptions contantly.
// Navigate to the course import page.
$this->execute("behat_navigation::i_am_on_page_instance", [$tocourse, 'import']);
// Select the course.
$fromcourse = behat_context_helper::escape($fromcourse);
$xpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' ics-results ')]" .
"/descendant::tr[contains(., $fromcourse)]" .
"/descendant::input[@type='radio']";
$this->execute('behat_forms::i_set_the_field_with_xpath_to', [$xpath, 1]);
$this->execute("behat_forms::press_button", get_string('continue'));
// Initial settings.
$this->fill_backup_restore_form($this->get_step_options($options, "Initial"));
$this->execute("behat_forms::press_button", get_string('importbackupstage1action', 'backup'));
// Schema settings.
$this->fill_backup_restore_form($this->get_step_options($options, "Schema"));
$this->execute("behat_forms::press_button", get_string('importbackupstage2action', 'backup'));
// Run it.
$this->execute("behat_forms::press_button", get_string('importbackupstage4action', 'backup'));
// Wait to ensure restore is complete.
$this->execute("behat_general::wait_until_the_page_is_ready");
// Continue and redirect to 'to' course.
$this->execute("behat_general::i_click_on", array(get_string('continue'), 'button'));
}
/**
* Restores the backup into the specified course and the provided options.
*
* You should be in the 'Restore' page where the backup is.
*
* @Given /^I restore "(?P<backup_filename_string>(?:[^"]|\\")*)" backup into "(?P<existing_course_fullname_string>(?:[^"]|\\")*)" course using this options:$/
* @param string $backupfilename
* @param string $existingcourse
* @param TableNode $options Restore forms options or false if no options provided
*/
public function i_restore_backup_into_course_using_this_options($backupfilename, $existingcourse, $options = false) {
// Confirm restore.
$this->select_backup($backupfilename);
// The argument should be converted to an xpath literal.
$existingcourse = behat_context_helper::escape($existingcourse);
// Selecting the specified course (we can not call behat_forms::select_radio here as is in another behat subcontext).
$radionodexpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' bcs-existing-course ')]" .
"/descendant::div[contains(concat(' ', normalize-space(@class), ' '), ' restore-course-search ')]" .
"/descendant::tr[contains(., $existingcourse)]" .
"/descendant::input[@type='radio']";
$this->execute("behat_general::i_click_on", array($radionodexpath, 'xpath_element'));
// Pressing the continue button of the restore into an existing course section.
$continuenodexpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' bcs-existing-course ')]" .
"/descendant::input[@type='submit'][@value='" . get_string('continue') . "']";
$this->execute("behat_general::i_click_on", array($continuenodexpath, 'xpath_element'));
// Common restore process using provided key/value options.
$this->process_restore($options);
}
/**
* Restores the specified backup into a new course using the provided options.
*
* You should be in the 'Restore' page where the backup is.
*
* @Given /^I restore "(?P<backup_filename_string>(?:[^"]|\\")*)" backup into a new course using this options:$/
* @param string $backupfilename
* @param TableNode $options Restore forms options or false if no options provided
*/
public function i_restore_backup_into_a_new_course_using_this_options($backupfilename, $options = false) {
// Confirm restore.
$this->select_backup($backupfilename);
// The first category in the list.
$radionodexpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' bcs-new-course ')]" .
"/descendant::div[contains(concat(' ', normalize-space(@class), ' '), ' restore-course-search ')]" .
"/descendant::input[@type='radio']";
$this->execute("behat_general::i_click_on", array($radionodexpath, 'xpath_element'));
// Pressing the continue button of the restore into an existing course section.
$continuenodexpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' bcs-new-course ')]" .
"/descendant::input[@type='submit'][@value='" . get_string('continue') . "']";
$this->execute("behat_general::i_click_on", array($continuenodexpath, 'xpath_element'));
// Common restore process using provided key/value options.
$this->process_restore($options);
}
/**
* Merges the backup into the current course using the provided restore options.
*
* You should be in the 'Restore' page where the backup is.
*
* @Given /^I merge "(?P<backup_filename_string>(?:[^"]|\\")*)" backup into the current course using this options:$/
* @param string $backupfilename
* @param TableNode $options Restore forms options or false if no options provided
*/
public function i_merge_backup_into_the_current_course($backupfilename, $options = false) {
// Confirm restore.
$this->select_backup($backupfilename);
// Merge without deleting radio option.
$radionodexpath = "//div[contains(concat(' ', normalize-space(@class), ' '), 'bcs-current-course')]" .
"/descendant::input[@type='radio'][@name='target'][@value='1']";
$this->execute("behat_general::i_click_on", array($radionodexpath, 'xpath_element'));
// Pressing the continue button of the restore merging section.
$continuenodexpath = "//div[contains(concat(' ', normalize-space(@class), ' '), 'bcs-current-course')]" .
"/descendant::input[@type='submit'][@value='" . get_string('continue') . "']";
$this->execute("behat_general::i_click_on", array($continuenodexpath, 'xpath_element'));
// Common restore process using provided key/value options.
$this->process_restore($options);
}
/**
* Merges the backup into the current course after deleting this contents, using the provided restore options.
*
* You should be in the 'Restore' page where the backup is.
*
* @Given /^I merge "(?P<backup_filename_string>(?:[^"]|\\")*)" backup into the current course after deleting it's contents using this options:$/
* @param string $backupfilename
* @param TableNode $options Restore forms options or false if no options provided
*/
public function i_merge_backup_into_current_course_deleting_its_contents($backupfilename, $options = false) {
// Confirm restore.
$this->select_backup($backupfilename);
// Delete contents radio option.
$radionodexpath = "//div[contains(concat(' ', normalize-space(@class), ' '), 'bcs-current-course')]" .
"/descendant::input[@type='radio'][@name='target'][@value='0']";
$this->execute("behat_general::i_click_on", array($radionodexpath, 'xpath_element'));
// Pressing the continue button of the restore merging section.
$continuenodexpath = "//div[contains(concat(' ', normalize-space(@class), ' '), 'bcs-current-course')]" .
"/descendant::input[@type='submit'][@value='" . get_string('continue') . "']";
$this->execute("behat_general::i_click_on", array($continuenodexpath, 'xpath_element'));
// Common restore process using provided key/value options.
$this->process_restore($options);
}
/**
* Selects the backup to restore.
*
* @throws ExpectationException
* @param string $backupfilename
* @return void
*/
protected function select_backup($backupfilename) {
// Using xpath as there are other restore links before this one.
$exception = new ExpectationException('The "' . $backupfilename . '" backup file can not be found in this page',
$this->getSession());
// The argument should be converted to an xpath literal.
$backupfilename = behat_context_helper::escape($backupfilename);
$xpath = "//tr[contains(., $backupfilename)]/descendant::a[contains(., '" . get_string('restore') . "')]";
$restorelink = $this->find('xpath', $xpath, $exception);
$restorelink->click();
// Confirm the backup contents.
$this->find_button(get_string('continue'))->press();
}
/**
* Executes the common steps of all restore processes.
*
* @param TableNode $options The backup and restore options or false if no options provided
* @return void
*/
protected function process_restore($options) {
// We can not use other steps here as we don't know where the provided data
// table elements are used, and we need to catch exceptions contantly.
// Settings.
$this->fill_backup_restore_form($this->get_step_options($options, "Settings"));
$this->execute("behat_forms::press_button", get_string('restorestage4action', 'backup'));
// Schema.
$this->fill_backup_restore_form($this->get_step_options($options, "Schema"));
$this->execute("behat_forms::press_button", get_string('restorestage8action', 'backup'));
// Review, no options here.
$this->execute("behat_forms::press_button", get_string('restorestage16action', 'backup'));
// Wait till the final button is visible.
$this->execute("behat_general::wait_until_the_page_is_ready");
// Last restore continue button, redirected to restore course after this.
$this->execute("behat_general::i_click_on", array(get_string('restorestage32action', 'backup'), 'button'));
}
/**
* Tries to fill the current page form elements with the provided options.
*
* This step is slow as it spins over each provided option, we are
* not expected to have lots of provided options, anyways, is better
* to be conservative and wait for the elements to appear rather than
* to have false failures.
*
* @param TableNode $options The backup and restore options or false if no options provided
* @return void
*/
protected function fill_backup_restore_form($options) {
// Nothing to fill if no options are provided.
if (!$options) {
return;
}
// If we find any of the provided options in the current form we should set the value.
$datahash = $options->getRowsHash();
foreach ($datahash as $locator => $value) {
$field = behat_field_manager::get_form_field_from_label($locator, $this);
$field->set_value($value);
}
}
/**
* Get the options specific to this step of the backup/restore process.
*
* @param TableNode $options The options table to filter
* @param string $step The name of the step
* @return TableNode The filtered options table
* @throws ExpectationException
*/
protected function get_step_options($options, $step) {
// Nothing to fill if no options are provided.
if (!$options) {
return;
}
$rows = $options->getRows();
$newrows = array();
foreach ($rows as $k => $data) {
if (count($data) !== 3) {
// Not enough information to guess the page.
throw new ExpectationException("The backup/restore step must be specified for all backup options",
$this->getSession());
} else if ($data[0] == $step) {
unset($data[0]);
$newrows[] = $data;
}
}
$pageoptions = new TableNode($newrows);
return $pageoptions;
}
}
@@ -0,0 +1,40 @@
@core @core_backup
Feature: Duplicate activities
In order to set up my course contents quickly
As a teacher
I need to duplicate activities inside the same course
Scenario: Duplicate an activity
Given the following "course" exists:
| fullname | Course 1 |
| shortname | C1 |
| category | 0 |
| initsections | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And the following "activities" exist:
| activity | name | intro | course | idnumber | section |
| data | Test database name | Test database description | C1 | database1 | 1 |
And the following config values are set as admin:
| backup_import_activities | 0 | backup |
And I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
And I duplicate "Test database name" activity
And I should see "Test database name (copy)"
And I wait until section "1" is available
And I click on "Edit settings" "link" in the "Test database name" activity
And I set the following fields to these values:
| Name | Original database name |
And I press "Save and return to course"
And I click on "Edit settings" "link" in the "Test database name (copy)" activity
And I set the following fields to these values:
| Name | Duplicated database name |
| Description | Duplicated database description |
And I press "Save and return to course"
Then I should see "Original database name" in the "Section 1" "section"
And I should see "Duplicated database name" in the "Section 1" "section"
And "Original database name" "link" should appear before "Duplicated database name" "link"
@@ -0,0 +1,65 @@
@core @core_backup @core_contentbank @core_h5p @contenttype_h5p @_file_upload @javascript
Feature: Import course content bank content
In order to import content from a course contentbank
As a teacher
I need to confirm that errors will not happen
Background:
Given I log in as "admin"
And the following config values are set as admin:
| unaddableblocks | | theme_boost|
And I am on site homepage
And I turn editing mode on
And I add the "Navigation" block if not present
And I configure the "Navigation" block
And I set the following fields to these values:
| Page contexts | Display throughout the entire site |
And I press "Save changes"
And I navigate to "H5P > Manage H5P content types" in site administration
And I upload "h5p/tests/fixtures/ipsums.h5p" file to "H5P content type" filemanager
And I click on "Upload H5P content types" "button" in the "#fitem_id_uploadlibraries" "css_element"
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
| Course 2 | C2 | 0 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| teacher1 | C2 | editingteacher |
And the following "contentbank content" exist:
| contextlevel | reference | contenttype | user | contentname | filepath |
| Course | C1 | contenttype_h5p | teacher1 | ipsums.h5p | /h5p/tests/fixtures/ipsums.h5p |
And I log out
And I log in as "teacher1"
Scenario: Import content bank content to another course
Given I am on "Course 2" course homepage
And I expand "Site pages" node
And I click on "Content bank" "link"
And I should not see "ipsums.h5p"
When I import "Course 1" course into "Course 2" course using this options:
And I expand "Site pages" node
And I click on "Content bank" "link"
Then I should see "ipsums.h5p"
And I am on "Course 1" course homepage
And I expand "Site pages" node
And I click on "Content bank" "link"
And I should see "ipsums.h5p"
Scenario: User could configure not to import content bank
Given I am on "Course 2" course homepage
And I expand "Site pages" node
And I click on "Content bank" "link"
And I should not see "ipsums.h5p"
When I import "Course 1" course into "Course 2" course using this options:
| Initial | Include content bank content | 0 |
And I expand "Site pages" node
And I click on "Content bank" "link"
Then I should not see "ipsums.h5p"
And I am on "Course 1" course homepage
And I expand "Site pages" node
And I click on "Content bank" "link"
And I should see "ipsums.h5p"
@@ -0,0 +1,57 @@
@core @core_backup
Feature: Import course's contents into another course
In order to move and copy contents between courses
As a teacher
I need to import a course contents into another course selecting what I want to import
Background:
Given the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
| Course 2 | C2 | 0 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| teacher1 | C2 | editingteacher |
Scenario: Import course's contents to another course
Given I log in as "teacher1"
And the following "activities" exist:
| activity | name | course | idnumber |
| data | Test database name | C1 | database1 |
| forum | Test forum name | C1 | forum1 |
And the following "blocks" exist:
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
| comments | Course | C1 | course-view-* | side-pre |
| blog_recent | Course | C1 | course-view-* | side-pre |
When I import "Course 1" course into "Course 2" course using this options:
Then I should see "Test database name"
And I should see "Test forum name"
And I should see "Comments" in the "Comments" "block"
And I should see "Recent blog entries"
Scenario: Import process with permission option
Given the following "permission overrides" exist:
| capability | permission | role | contextlevel | reference |
| enrol/manual:enrol | Allow | teacher | Course | C1 |
And I log in as "teacher1"
When I import "Course 1" course into "Course 2" course using this options:
| Initial | Include permission overrides | 1 |
And I am on the "Course 1" "permissions" page
Then I should see "Non-editing teacher (1)"
And I set the field "Advanced role override" to "Non-editing teacher (1)"
And I click on "//div[@class='advancedoverride']/div/form/noscript/input" "xpath_element"
And "enrol/manual:enrol" capability has "Allow" permission
Scenario: Import process without permission option
Given the following "permission overrides" exist:
| capability | permission | role | contextlevel | reference |
| enrol/manual:enrol | Allow | teacher | Course | C1 |
And I log in as "teacher1"
When I import "Course 1" course into "Course 2" course using this options:
| Initial | Include permission overrides | 0 |
And I am on the "Course 2" "permissions" page
Then I should see "Non-editing teacher (0)"
@@ -0,0 +1,48 @@
@core @core_backup
Feature: Option to include groups and groupings when importing a course to another course
In order to import a course to another course with groups and groupings
As a teacher
I need an option to include groups and groupings when importing a course to another course
Background:
Given the following "courses" exist:
| fullname | shortname |
| Course 1 | C1 |
| Course 2 | C2 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| teacher1 | C2 | editingteacher |
And the following "groups" exist:
| name | description | course | idnumber |
| Group 1 | Group description | C1 | GROUP1 |
| Group 2 | Group description | C1 | GROUP2 |
And the following "groupings" exist:
| name | course | idnumber |
| Grouping 1 | C1 | GROUPING1 |
| Grouping 2 | C1 | GROUPING2 |
And I log in as "teacher1"
And I am on "Course 1" course homepage
Scenario: Include groups and groupings when importing a course to another course
Given I import "Course 1" course into "Course 2" course using this options:
| Initial | Include groups and groupings | 1 |
When I am on the "Course 2" "groups" page
Then I should see "Group 1"
And I should see "Group 2"
And I am on the "Course 2" "groupings" page
And I should see "Grouping 1"
And I should see "Grouping 2"
Scenario: Do not include groups and groupings when importing a course to another course
Given I import "Course 1" course into "Course 2" course using this options:
| Initial | Include groups and groupings | 0 |
When I am on the "Course 2" "groups" page
Then I should not see "Group 1"
And I should not see "Group 2"
And I am on the "Course 2" "groupings" page
And I should not see "Grouping 1"
And I should not see "Grouping 2"
@@ -0,0 +1,257 @@
@core @core_backup
Feature: Restore Moodle 2 course backups
In order to continue using my stored course contents
As a teacher and an admin
I need to restore them inside other Moodle courses or in new courses
Background:
Given the following "courses" exist:
| fullname | shortname | category | format | numsections | coursedisplay | initsections |
| Course 1 | C1 | 0 | topics | 15 | 1 | 1 |
| Course 2 | C2 | 0 | topics | 5 | 0 | 1 |
| Course 3 | C3 | 0 | topics | 2 | 0 | 1 |
| Course 4 | C4 | 0 | topics | 20 | 0 | 1 |
| Course 5 | C5 | 0 | topics | 15 | 1 | 0 |
And the following "activities" exist:
| activity | course | idnumber | name | intro | section | externalurl |
| assign | C3 | assign1 | Test assign name | Assign description | 1 | |
| data | C3 | data1 | Test database name | Database description | 2 | |
| forum | C1 | 0001 | Test forum name | | 1 | |
| url | C1 | url1 | Test URL name | Test URL description | 3 | http://www.moodle.org |
| forum | C5 | 0005 | Test forum name | | 1 | |
| url | C5 | url5 | Test URL name | Test URL description | 3 | http://www.moodle.org |
And the following "blocks" exist:
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
| activity_modules | Course | C1 | course-view-* | side-pre |
| activity_modules | Course | C5 | course-view-* | side-pre |
And the following config values are set as admin:
| enableasyncbackup | 0 |
And I log in as "admin"
And I am on "Course 1" course homepage with editing mode on
@javascript
Scenario: Restore a course in another existing course
When I backup "Course 1" course using this options:
| Confirmation | Filename | test_backup.mbz |
And I restore "test_backup.mbz" backup into "Course 2" course using this options:
Then I should see "Course 2"
And I should see "Activities" in the "Activities" "block"
And I should see "Test forum name"
@javascript
Scenario: Restore a course in a new course
When I backup "Course 1" course using this options:
| Confirmation | Filename | test_backup.mbz |
And I restore "test_backup.mbz" backup into a new course using this options:
| Schema | Course name | Course 1 restored in a new course |
Then I should see "Course 1 restored in a new course"
And I should see "Activities" in the "Activities" "block"
And I should see "Test forum name"
And I should see "Section 15"
And I should not see "Section 16"
And I navigate to "Settings" in current page administration
And I expand all fieldsets
And the field "id_format" matches value "Custom sections"
And I press "Cancel"
@javascript
Scenario: Restore a backup into the same course
When I backup "Course 3" course using this options:
| Confirmation | Filename | test_backup.mbz |
And I restore "test_backup.mbz" backup into "Course 2" course using this options:
| Schema | Test database name | 0 |
| Schema | Section 2 | 0 |
Then I should see "Course 2"
And I should see "Test assign name"
And I should not see "Test database name"
@javascript
Scenario: Restore a backup into the same course removing it's contents before that
When I backup "Course 1" course using this options:
| Confirmation | Filename | test_backup.mbz |
And the following "activity" exists:
| activity | forum |
| course | C1 |
| section | 1 |
| name | Test forum post backup name |
And I am on the "Course 1" "restore" page
And I merge "test_backup.mbz" backup into the current course after deleting it's contents using this options:
| Schema | Section 3 | 0 |
Then I should see "Course 1"
And I should not see "Section 3"
And I should not see "Test forum post backup name"
And I should see "Activities" in the "Activities" "block"
And I should see "Test forum name"
@javascript
Scenario: Restore a backup into a new course changing the course format afterwards
Given I backup "Course 5" course using this options:
| Confirmation | Filename | test_backup.mbz |
When I restore "test_backup.mbz" backup into a new course using this options:
Then I should see "New section"
And I should see "Test forum name"
And I navigate to "Settings" in current page administration
And I expand all fieldsets
And the field "id_format" matches value "Custom sections"
And I set the following fields to these values:
| id_startdate_day | 1 |
| id_startdate_month | January |
| id_startdate_year | 2020 |
| id_format | Weekly sections |
| id_enddate_enabled | 0 |
And I press "Save and display"
And I should see "1 January - 7 January"
And I should see "Test forum name"
And I navigate to "Settings" in current page administration
And I expand all fieldsets
And the field "id_format" matches value "Weekly sections"
And I set the following fields to these values:
| id_format | Social |
And I press "Save and display"
And I should see "An open forum for chatting about anything you want to"
And I navigate to "Settings" in current page administration
And I expand all fieldsets
And the field "id_format" matches value "Social"
And I press "Cancel"
@javascript
Scenario: Restore a backup in an existing course retaining the backup course settings
Given I hide section "3"
And I hide section "7"
When I backup "Course 1" course using this options:
| Confirmation | Filename | test_backup.mbz |
And I restore "test_backup.mbz" backup into "Course 2" course using this options:
| Schema | Overwrite course configuration | Yes |
And I navigate to "Settings" in current page administration
And I expand all fieldsets
Then the field "id_format" matches value "Custom sections"
And the field "Course layout" matches value "Show one section per page"
And the field "Course short name" matches value "C1_1"
And I press "Cancel"
And section "3" should be visible
And section "7" should be hidden
And section "15" should be visible
And I should see "Section 15"
And I should not see "Section 16"
And I should see "Test URL name" in the "Section 3" "section"
And I should see "Test forum name" in the "Section 1" "section"
@javascript
Scenario: Restore a backup in an existing course keeping the target course settings
Given I hide section "3"
And I hide section "7"
When I backup "Course 1" course using this options:
| Confirmation | Filename | test_backup.mbz |
And I restore "test_backup.mbz" backup into "Course 2" course using this options:
| Schema | Overwrite course configuration | No |
And I navigate to "Settings" in current page administration
And I expand all fieldsets
Then the field "id_format" matches value "Custom sections"
And the field "Course short name" matches value "C2"
And the field "Course layout" matches value "Show all sections on one page"
And I press "Cancel"
And section "3" should be visible
And section "7" should be hidden
And section "15" should be visible
And I should see "Section 15"
And I should not see "Section 16"
And I should see "Test URL name" in the "Section 3" "section"
And I should see "Test forum name" in the "Section 1" "section"
@javascript
Scenario: Restore a backup in an existing course deleting contents and retaining the backup course settings
Given I hide section "3"
And I hide section "7"
When I backup "Course 1" course using this options:
| Initial | Include enrolled users | 0 |
| Confirmation | Filename | test_backup.mbz |
And I am on the "Course 2" "restore" page
And I merge "test_backup.mbz" backup into the current course after deleting it's contents using this options:
| Schema | Overwrite course configuration | Yes |
And I navigate to "Settings" in current page administration
And I expand all fieldsets
Then the field "id_format" matches value "Custom sections"
And the field "Course layout" matches value "Show one section per page"
And the field "Course short name" matches value "C1_1"
And I press "Cancel"
And section "3" should be hidden
And section "7" should be hidden
And section "15" should be visible
And I should see "Section 15"
And I should not see "Section 16"
And I should see "Test URL name" in the "Section 3" "section"
And I should see "Test forum name" in the "Section 1" "section"
@javascript
Scenario: Restore a backup in an existing course deleting contents and keeping the current course settings
Given I hide section "3"
And I hide section "7"
When I backup "Course 1" course using this options:
| Initial | Include enrolled users | 0 |
| Confirmation | Filename | test_backup.mbz |
And I am on the "Course 2" "restore" page
And I merge "test_backup.mbz" backup into the current course after deleting it's contents using this options:
| Schema | Overwrite course configuration | No |
And I navigate to "Settings" in current page administration
And I expand all fieldsets
Then the field "id_format" matches value "Custom sections"
And the field "Course short name" matches value "C2"
And the field "Course layout" matches value "Show all sections on one page"
And I press "Cancel"
And section "3" should be hidden
And section "7" should be hidden
And section "15" should be visible
And I should see "Section 15"
And I should not see "Section 16"
And I should see "Test URL name" in the "Section 3" "section"
And I should see "Test forum name" in the "Section 1" "section"
@javascript
Scenario: Restore a backup in an existing course deleting contents decreasing the number of sections
Given I hide section "3"
And I hide section "7"
When I backup "Course 1" course using this options:
| Initial | Include enrolled users | 0 |
| Confirmation | Filename | test_backup.mbz |
And I am on the "Course 4" "restore" page
And I merge "test_backup.mbz" backup into the current course after deleting it's contents using this options:
| Schema | Overwrite course configuration | No |
And I navigate to "Settings" in current page administration
And I expand all fieldsets
Then the field "id_format" matches value "Custom sections"
And the field "Course short name" matches value "C4"
And the field "Course layout" matches value "Show all sections on one page"
And I press "Cancel"
And section "3" should be hidden
And section "7" should be hidden
And section "15" should be visible
And I should see "Section 15"
And I should not see "Section 16"
And I should see "Test URL name" in the "Section 3" "section"
And I should see "Test forum name" in the "Section 1" "section"
@javascript
Scenario: Restore a backup with override permission
Given the following "permission overrides" exist:
| capability | permission | role | contextlevel | reference |
| enrol/manual:enrol | Allow | teacher | Course | C1 |
And I backup "Course 1" course using this options:
| Confirmation | Filename | test_backup.mbz |
When I restore "test_backup.mbz" backup into a new course using this options:
| Settings | Include permission overrides | 1 |
Then I am on the "Course 1 copy 1" "permissions" page
And I should see "Non-editing teacher (1)"
And I set the field "Advanced role override" to "Non-editing teacher (1)"
And "enrol/manual:enrol" capability has "Allow" permission
@javascript
Scenario: Restore a backup without override permission
Given the following "permission overrides" exist:
| capability | permission | role | contextlevel | reference |
| enrol/manual:enrol | Allow | teacher | Course | C1 |
And I backup "Course 1" course using this options:
| Confirmation | Filename | test_backup.mbz |
When I restore "test_backup.mbz" backup into a new course using this options:
| Settings | Include permission overrides | 0 |
Then I am on the "Course 1 copy 1" "permissions" page
And I should see "Non-editing teacher (0)"
@@ -0,0 +1,124 @@
@core @core_backup
Feature: Restore Moodle 2 course backups with different user data settings
In order to decide upon including user data during backup and restore of courses
As a teacher and an admin
I need to be able to set and override backup and restore settings
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@example.com |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "activities" exist:
| activity | name | intro | course | idnumber |
| data | Test database name | n | C1 | data1 |
And the following "mod_data > fields" exist:
| database | type | name | description |
| data1 | text | Test field name | Test field description |
And the following "mod_data > templates" exist:
| database | name |
| data1 | singletemplate |
| data1 | listtemplate |
| data1 | addtemplate |
| data1 | asearchtemplate |
| data1 | rsstemplate |
And the following "mod_data > entries" exist:
| database | user | Test field name |
| data1 | student1 | Student entry |
And the following config values are set as admin:
| enableasyncbackup | 0 |
And I log in as "admin"
And I backup "Course 1" course using this options:
| Initial | Include enrolled users | 1 |
| Confirmation | Filename | test_backup.mbz |
@javascript
Scenario: Restore a backup with user data
# "User data" marks the user data field for the section
# "-" marks the user data field for the data activity
When I restore "test_backup.mbz" backup into a new course using this options:
| Settings | Include enrolled users | 1 |
| Schema | User data | 1 |
| Schema | - | 1 |
Then I should see "Test database name"
When I click on "Test database name" "link" in the "region-main" "region"
Then I should see "Student entry"
@javascript
Scenario: Restore a backup without user data for data activity
# "User data" marks the user data field for the section
# "-" marks the user data field for the data activity
When I restore "test_backup.mbz" backup into a new course using this options:
| Settings | Include enrolled users | 1 |
| Schema | User data | 1 |
| Schema | - | 0 |
Then I should see "Test database name"
When I click on "Test database name" "link" in the "region-main" "region"
Then I should not see "Student entry"
@javascript
Scenario: Restore a backup without user data for section and data activity
# "User data" marks the user data field for the section
# "-" marks the user data field for the data activity
When I restore "test_backup.mbz" backup into a new course using this options:
| Settings | Include enrolled users | 1 |
| Schema | User data | 0 |
| Schema | - | 0 |
Then I should see "Test database name"
When I click on "Test database name" "link" in the "region-main" "region"
Then I should not see "Student entry"
@javascript
Scenario: Restore a backup without user data for section
# "User data" marks the user data field for the section
# "-" marks the user data field for the data activity
When I restore "test_backup.mbz" backup into a new course using this options:
| Settings | Include enrolled users | 1 |
| Schema | - | 1 |
| Schema | User data | 0 |
Then I should see "Test database name"
When I click on "Test database name" "link" in the "region-main" "region"
Then I should not see "Student entry"
@javascript
Scenario: Restore a backup with user data with local config for including users set to 0
And I restore "test_backup.mbz" backup into a new course using this options:
| Settings | Include enrolled users | 0 |
Then I should see "Test database name"
When I click on "Test database name" "link" in the "region-main" "region"
Then I should not see "Student entry"
@javascript
Scenario: Restore a backup with user data with site config for including users set to 0
Given I navigate to "Courses > Backups > General restore defaults" in site administration
And I set the field "s_restore_restore_general_users" to ""
And I press "Save changes"
And I am on the "Course 1" "restore" page
# "User data" marks the user data field for the section
# "-" marks the user data field for the data activity
And I restore "test_backup.mbz" backup into a new course using this options:
| Settings | Include enrolled users | 1 |
| Schema | User data | 1 |
| Schema | - | 1 |
Then I should see "Test database name"
When I click on "Test database name" "link" in the "region-main" "region"
Then I should see "Student entry"
@javascript
Scenario: Restore a backup with user data with local and site config config for including users set to 0
Given I navigate to "Courses > Backups > General restore defaults" in site administration
And I set the field "s_restore_restore_general_users" to ""
And I press "Save changes"
And I am on the "Course 1" "restore" page
When I restore "test_backup.mbz" backup into a new course using this options:
| Settings | Include enrolled users | 0 |
Then I should see "Test database name"
When I click on "Test database name" "link" in the "region-main" "region"
Then I should not see "Student entry"
+33
View File
@@ -0,0 +1,33 @@
<?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/>.
namespace core_backup;
/**
* ui tests (all)
*
* @package core_backup
* @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class ui_test extends \basic_testcase {
/**
* Test backup_ui class
*/
public function test_backup_ui(): void {
}
}
@@ -0,0 +1,193 @@
YUI.add('moodle-backup-backupselectall', function (Y, NAME) {
/**
* Adds select all/none links to the top of the backup/restore/import schema page.
*
* @module moodle-backup-backupselectall
*/
// Namespace for the backup
M.core_backup = M.core_backup || {};
/**
* Adds select all/none links to the top of the backup/restore/import schema page.
*
* @class M.core_backup.backupselectall
*/
M.core_backup.backupselectall = function(modnames) {
var formid = null;
var helper = function(e, check, type, mod) {
e.preventDefault();
var prefix = '';
if (typeof mod !== 'undefined') {
prefix = 'setting_activity_' + mod + '_';
}
var len = type.length;
Y.all('input[type="checkbox"]').each(function(checkbox) {
var name = checkbox.get('name');
// If a prefix has been set, ignore checkboxes which don't have that prefix.
if (prefix && name.substring(0, prefix.length) !== prefix) {
return;
}
if (name.substring(name.length - len) === type) {
checkbox.set('checked', check);
}
});
// At this point, we really need to persuade the form we are part of to
// update all of its disabledIf rules. However, as far as I can see,
// given the way that lib/form/form.js is written, that is impossible.
if (formid && M.form) {
M.form.updateFormState(formid);
}
};
var html_generator = function(classname, idtype, heading, extra) {
if (typeof extra === 'undefined') {
extra = '';
}
return '<div class="' + classname + '">' +
'<div class="fitem fitem_fcheckbox backup_selector">' +
'<div class="fitemtitle">' + heading + '</div>' +
'<div class="felement">' +
'<a id="backup-all-' + idtype + '" href="#">' + M.util.get_string('all', 'moodle') + '</a> / ' +
'<a id="backup-none-' + idtype + '" href="#">' + M.util.get_string('none', 'moodle') + '</a>' +
extra +
'</div>' +
'</div>' +
'</div>';
};
var firstsection = Y.one('fieldset#id_coursesettings .fcontainer .grouped_settings.section_level');
if (!firstsection) {
// This is not a relevant page.
return;
}
if (!firstsection.one('input[type="checkbox"]')) {
// No checkboxes.
return;
}
formid = firstsection.ancestor('form').getAttribute('id');
var withuserdata = false;
Y.all('input[type="checkbox"]').each(function(checkbox) {
var name = checkbox.get('name');
if (name.substring(name.length - 9) === '_userdata') {
withuserdata = '_userdata';
} else if (name.substring(name.length - 9) === '_userinfo') {
withuserdata = '_userinfo';
}
});
// Add global select all/none options.
var html = html_generator('include_setting section_level', 'included', M.util.get_string('select', 'moodle'),
' (<a id="backup-bytype" href="#">' + M.util.get_string('showtypes', 'backup') + '</a>)');
if (withuserdata) {
html += html_generator('normal_setting', 'userdata', M.util.get_string('select', 'moodle'));
}
var links = Y.Node.create('<div class="grouped_settings section_level">' + html + '</div>');
firstsection.insert(links, 'before');
// Add select all/none for each module type.
var initlinks = function(links, mod) {
Y.one('#backup-all-mod_' + mod).on('click', function(e) {
helper(e, true, '_included', mod);
});
Y.one('#backup-none-mod_' + mod).on('click', function(e) {
helper(e, false, '_included', mod);
});
if (withuserdata) {
Y.one('#backup-all-userdata-mod_' + mod).on('click', function(e) {
helper(e, true, withuserdata, mod);
});
Y.one('#backup-none-userdata-mod_' + mod).on('click', function(e) {
helper(e, false, withuserdata, mod);
});
}
};
// For each module type on the course, add hidden select all/none options.
var modlist = Y.Node.create('<div id="mod_select_links">');
modlist.hide();
modlist.currentlyshown = false;
links.appendChild(modlist);
for (var mod in modnames) {
// Only include actual values from the list.
if (!modnames.hasOwnProperty(mod)) {
continue;
}
html = html_generator('include_setting section_level', 'mod_' + mod, modnames[mod]);
if (withuserdata) {
html += html_generator('normal_setting', 'userdata-mod_' + mod, modnames[mod]);
}
var modlinks = Y.Node.create(
'<div class="grouped_settings section_level">' + html + '</div>');
modlist.appendChild(modlinks);
initlinks(modlinks, mod);
}
// Toggles the display of the hidden module select all/none links.
var toggletypes = function() {
// Change text of type toggle link.
var link = Y.one('#backup-bytype');
if (modlist.currentlyshown) {
link.setHTML(M.util.get_string('showtypes', 'backup'));
} else {
link.setHTML(M.util.get_string('hidetypes', 'backup'));
}
// The link has now been toggled (from show to hide, or vice-versa).
modlist.currentlyshown = !modlist.currentlyshown;
// Either hide or show the links.
var animcfg = {node: modlist, duration: 0.2},
anim;
if (modlist.currentlyshown) {
// Animate reveal of the module links.
modlist.show();
animcfg.to = {maxHeight: modlist.get('clientHeight') + 'px'};
modlist.setStyle('maxHeight', '0px');
anim = new Y.Anim(animcfg);
anim.on('end', function() {
modlist.setStyle('maxHeight', 'none');
});
anim.run();
} else {
// Animate hide of the module links.
animcfg.to = {maxHeight: '0px'};
modlist.setStyle('maxHeight', modlist.get('clientHeight') + 'px');
anim = new Y.Anim(animcfg);
anim.on('end', function() {
modlist.hide();
modlist.setStyle('maxHeight', 'none');
});
anim.run();
}
};
Y.one('#backup-bytype').on('click', function(e) {
e.preventDefault();
toggletypes();
});
Y.one('#backup-all-included').on('click', function(e) {
helper(e, true, '_included');
});
Y.one('#backup-none-included').on('click', function(e) {
helper(e, false, '_included');
});
if (withuserdata) {
Y.one('#backup-all-userdata').on('click', function(e) {
helper(e, true, withuserdata);
});
Y.one('#backup-none-userdata').on('click', function(e) {
helper(e, false, withuserdata);
});
}
};
}, '@VERSION@', {"requires": ["node", "event", "node-event-simulate", "anim"]});
@@ -0,0 +1 @@
YUI.add("moodle-backup-backupselectall",function(g,e){M.core_backup=M.core_backup||{},M.core_backup.backupselectall=function(e){var t,n,i,d,o,c,r,p,l=null,a=function(e,t,i,n){var o,c;e.preventDefault(),o=void 0!==n?"setting_activity_"+n+"_":"",c=i.length,g.all('input[type="checkbox"]').each(function(e){var n=e.get("name");o&&n.substring(0,o.length)!==o||n.substring(n.length-c)===i&&e.set("checked",t)}),l&&M.form&&M.form.updateFormState(l)},u=function(e,n,t,i){return void 0===i&&(i=""),'<div class="'+e+'"><div class="fitem fitem_fcheckbox backup_selector"><div class="fitemtitle">'+t+'</div><div class="felement"><a id="backup-all-'+n+'" href="#">'+M.util.get_string("all","moodle")+'</a> / <a id="backup-none-'+n+'" href="#">'+M.util.get_string("none","moodle")+"</a>"+i+"</div></div></div>"},s=g.one("fieldset#id_coursesettings .fcontainer .grouped_settings.section_level");if(s&&s.one('input[type="checkbox"]')){for(c in l=s.ancestor("form").getAttribute("id"),t=!1,g.all('input[type="checkbox"]').each(function(e){e=e.get("name");"_userdata"===e.substring(e.length-9)?t="_userdata":"_userinfo"===e.substring(e.length-9)&&(t="_userinfo")}),n=u("include_setting section_level","included",M.util.get_string("select","moodle"),' (<a id="backup-bytype" href="#">'+M.util.get_string("showtypes","backup")+"</a>)"),t&&(n+=u("normal_setting","userdata",M.util.get_string("select","moodle"))),i=g.Node.create('<div class="grouped_settings section_level">'+n+"</div>"),s.insert(i,"before"),d=function(e,n){g.one("#backup-all-mod_"+n).on("click",function(e){a(e,!0,"_included",n)}),g.one("#backup-none-mod_"+n).on("click",function(e){a(e,!1,"_included",n)}),t&&(g.one("#backup-all-userdata-mod_"+n).on("click",function(e){a(e,!0,t,n)}),g.one("#backup-none-userdata-mod_"+n).on("click",function(e){a(e,!1,t,n)}))},(o=g.Node.create('<div id="mod_select_links">')).hide(),o.currentlyshown=!1,i.appendChild(o),e)e.hasOwnProperty(c)&&(n=u("include_setting section_level","mod_"+c,e[c]),t&&(n+=u("normal_setting","userdata-mod_"+c,e[c])),r=g.Node.create('<div class="grouped_settings section_level">'+n+"</div>"),o.appendChild(r),d(0,c));p=function(){var e,n=g.one("#backup-bytype");o.currentlyshown?n.setHTML(M.util.get_string("showtypes","backup")):n.setHTML(M.util.get_string("hidetypes","backup")),o.currentlyshown=!o.currentlyshown,n={node:o,duration:.2},o.currentlyshown?(o.show(),n.to={maxHeight:o.get("clientHeight")+"px"},o.setStyle("maxHeight","0px"),(e=new g.Anim(n)).on("end",function(){o.setStyle("maxHeight","none")})):(n.to={maxHeight:"0px"},o.setStyle("maxHeight",o.get("clientHeight")+"px"),(e=new g.Anim(n)).on("end",function(){o.hide(),o.setStyle("maxHeight","none")})),e.run()},g.one("#backup-bytype").on("click",function(e){e.preventDefault(),p()}),g.one("#backup-all-included").on("click",function(e){a(e,!0,"_included")}),g.one("#backup-none-included").on("click",function(e){a(e,!1,"_included")}),t&&(g.one("#backup-all-userdata").on("click",function(e){a(e,!0,t)}),g.one("#backup-none-userdata").on("click",function(e){a(e,!1,t)}))}}},"@VERSION@",{requires:["node","event","node-event-simulate","anim"]});
@@ -0,0 +1,193 @@
YUI.add('moodle-backup-backupselectall', function (Y, NAME) {
/**
* Adds select all/none links to the top of the backup/restore/import schema page.
*
* @module moodle-backup-backupselectall
*/
// Namespace for the backup
M.core_backup = M.core_backup || {};
/**
* Adds select all/none links to the top of the backup/restore/import schema page.
*
* @class M.core_backup.backupselectall
*/
M.core_backup.backupselectall = function(modnames) {
var formid = null;
var helper = function(e, check, type, mod) {
e.preventDefault();
var prefix = '';
if (typeof mod !== 'undefined') {
prefix = 'setting_activity_' + mod + '_';
}
var len = type.length;
Y.all('input[type="checkbox"]').each(function(checkbox) {
var name = checkbox.get('name');
// If a prefix has been set, ignore checkboxes which don't have that prefix.
if (prefix && name.substring(0, prefix.length) !== prefix) {
return;
}
if (name.substring(name.length - len) === type) {
checkbox.set('checked', check);
}
});
// At this point, we really need to persuade the form we are part of to
// update all of its disabledIf rules. However, as far as I can see,
// given the way that lib/form/form.js is written, that is impossible.
if (formid && M.form) {
M.form.updateFormState(formid);
}
};
var html_generator = function(classname, idtype, heading, extra) {
if (typeof extra === 'undefined') {
extra = '';
}
return '<div class="' + classname + '">' +
'<div class="fitem fitem_fcheckbox backup_selector">' +
'<div class="fitemtitle">' + heading + '</div>' +
'<div class="felement">' +
'<a id="backup-all-' + idtype + '" href="#">' + M.util.get_string('all', 'moodle') + '</a> / ' +
'<a id="backup-none-' + idtype + '" href="#">' + M.util.get_string('none', 'moodle') + '</a>' +
extra +
'</div>' +
'</div>' +
'</div>';
};
var firstsection = Y.one('fieldset#id_coursesettings .fcontainer .grouped_settings.section_level');
if (!firstsection) {
// This is not a relevant page.
return;
}
if (!firstsection.one('input[type="checkbox"]')) {
// No checkboxes.
return;
}
formid = firstsection.ancestor('form').getAttribute('id');
var withuserdata = false;
Y.all('input[type="checkbox"]').each(function(checkbox) {
var name = checkbox.get('name');
if (name.substring(name.length - 9) === '_userdata') {
withuserdata = '_userdata';
} else if (name.substring(name.length - 9) === '_userinfo') {
withuserdata = '_userinfo';
}
});
// Add global select all/none options.
var html = html_generator('include_setting section_level', 'included', M.util.get_string('select', 'moodle'),
' (<a id="backup-bytype" href="#">' + M.util.get_string('showtypes', 'backup') + '</a>)');
if (withuserdata) {
html += html_generator('normal_setting', 'userdata', M.util.get_string('select', 'moodle'));
}
var links = Y.Node.create('<div class="grouped_settings section_level">' + html + '</div>');
firstsection.insert(links, 'before');
// Add select all/none for each module type.
var initlinks = function(links, mod) {
Y.one('#backup-all-mod_' + mod).on('click', function(e) {
helper(e, true, '_included', mod);
});
Y.one('#backup-none-mod_' + mod).on('click', function(e) {
helper(e, false, '_included', mod);
});
if (withuserdata) {
Y.one('#backup-all-userdata-mod_' + mod).on('click', function(e) {
helper(e, true, withuserdata, mod);
});
Y.one('#backup-none-userdata-mod_' + mod).on('click', function(e) {
helper(e, false, withuserdata, mod);
});
}
};
// For each module type on the course, add hidden select all/none options.
var modlist = Y.Node.create('<div id="mod_select_links">');
modlist.hide();
modlist.currentlyshown = false;
links.appendChild(modlist);
for (var mod in modnames) {
// Only include actual values from the list.
if (!modnames.hasOwnProperty(mod)) {
continue;
}
html = html_generator('include_setting section_level', 'mod_' + mod, modnames[mod]);
if (withuserdata) {
html += html_generator('normal_setting', 'userdata-mod_' + mod, modnames[mod]);
}
var modlinks = Y.Node.create(
'<div class="grouped_settings section_level">' + html + '</div>');
modlist.appendChild(modlinks);
initlinks(modlinks, mod);
}
// Toggles the display of the hidden module select all/none links.
var toggletypes = function() {
// Change text of type toggle link.
var link = Y.one('#backup-bytype');
if (modlist.currentlyshown) {
link.setHTML(M.util.get_string('showtypes', 'backup'));
} else {
link.setHTML(M.util.get_string('hidetypes', 'backup'));
}
// The link has now been toggled (from show to hide, or vice-versa).
modlist.currentlyshown = !modlist.currentlyshown;
// Either hide or show the links.
var animcfg = {node: modlist, duration: 0.2},
anim;
if (modlist.currentlyshown) {
// Animate reveal of the module links.
modlist.show();
animcfg.to = {maxHeight: modlist.get('clientHeight') + 'px'};
modlist.setStyle('maxHeight', '0px');
anim = new Y.Anim(animcfg);
anim.on('end', function() {
modlist.setStyle('maxHeight', 'none');
});
anim.run();
} else {
// Animate hide of the module links.
animcfg.to = {maxHeight: '0px'};
modlist.setStyle('maxHeight', modlist.get('clientHeight') + 'px');
anim = new Y.Anim(animcfg);
anim.on('end', function() {
modlist.hide();
modlist.setStyle('maxHeight', 'none');
});
anim.run();
}
};
Y.one('#backup-bytype').on('click', function(e) {
e.preventDefault();
toggletypes();
});
Y.one('#backup-all-included').on('click', function(e) {
helper(e, true, '_included');
});
Y.one('#backup-none-included').on('click', function(e) {
helper(e, false, '_included');
});
if (withuserdata) {
Y.one('#backup-all-userdata').on('click', function(e) {
helper(e, true, withuserdata);
});
Y.one('#backup-none-userdata').on('click', function(e) {
helper(e, false, withuserdata);
});
}
};
}, '@VERSION@', {"requires": ["node", "event", "node-event-simulate", "anim"]});
@@ -0,0 +1,10 @@
{
"name": "moodle-backup-backupselectall",
"builds": {
"moodle-backup-backupselectall": {
"jsfiles": [
"backupselectall.js"
]
}
}
}
@@ -0,0 +1,188 @@
/**
* Adds select all/none links to the top of the backup/restore/import schema page.
*
* @module moodle-backup-backupselectall
*/
// Namespace for the backup
M.core_backup = M.core_backup || {};
/**
* Adds select all/none links to the top of the backup/restore/import schema page.
*
* @class M.core_backup.backupselectall
*/
M.core_backup.backupselectall = function(modnames) {
var formid = null;
var helper = function(e, check, type, mod) {
e.preventDefault();
var prefix = '';
if (typeof mod !== 'undefined') {
prefix = 'setting_activity_' + mod + '_';
}
var len = type.length;
Y.all('input[type="checkbox"]').each(function(checkbox) {
var name = checkbox.get('name');
// If a prefix has been set, ignore checkboxes which don't have that prefix.
if (prefix && name.substring(0, prefix.length) !== prefix) {
return;
}
if (name.substring(name.length - len) === type) {
checkbox.set('checked', check);
}
});
// At this point, we really need to persuade the form we are part of to
// update all of its disabledIf rules. However, as far as I can see,
// given the way that lib/form/form.js is written, that is impossible.
if (formid && M.form) {
M.form.updateFormState(formid);
}
};
var html_generator = function(classname, idtype, heading, extra) {
if (typeof extra === 'undefined') {
extra = '';
}
return '<div class="' + classname + '">' +
'<div class="fitem fitem_fcheckbox backup_selector">' +
'<div class="fitemtitle">' + heading + '</div>' +
'<div class="felement">' +
'<a id="backup-all-' + idtype + '" href="#">' + M.util.get_string('all', 'moodle') + '</a> / ' +
'<a id="backup-none-' + idtype + '" href="#">' + M.util.get_string('none', 'moodle') + '</a>' +
extra +
'</div>' +
'</div>' +
'</div>';
};
var firstsection = Y.one('fieldset#id_coursesettings .fcontainer .grouped_settings.section_level');
if (!firstsection) {
// This is not a relevant page.
return;
}
if (!firstsection.one('input[type="checkbox"]')) {
// No checkboxes.
return;
}
formid = firstsection.ancestor('form').getAttribute('id');
var withuserdata = false;
Y.all('input[type="checkbox"]').each(function(checkbox) {
var name = checkbox.get('name');
if (name.substring(name.length - 9) === '_userdata') {
withuserdata = '_userdata';
} else if (name.substring(name.length - 9) === '_userinfo') {
withuserdata = '_userinfo';
}
});
// Add global select all/none options.
var html = html_generator('include_setting section_level', 'included', M.util.get_string('select', 'moodle'),
' (<a id="backup-bytype" href="#">' + M.util.get_string('showtypes', 'backup') + '</a>)');
if (withuserdata) {
html += html_generator('normal_setting', 'userdata', M.util.get_string('select', 'moodle'));
}
var links = Y.Node.create('<div class="grouped_settings section_level">' + html + '</div>');
firstsection.insert(links, 'before');
// Add select all/none for each module type.
var initlinks = function(links, mod) {
Y.one('#backup-all-mod_' + mod).on('click', function(e) {
helper(e, true, '_included', mod);
});
Y.one('#backup-none-mod_' + mod).on('click', function(e) {
helper(e, false, '_included', mod);
});
if (withuserdata) {
Y.one('#backup-all-userdata-mod_' + mod).on('click', function(e) {
helper(e, true, withuserdata, mod);
});
Y.one('#backup-none-userdata-mod_' + mod).on('click', function(e) {
helper(e, false, withuserdata, mod);
});
}
};
// For each module type on the course, add hidden select all/none options.
var modlist = Y.Node.create('<div id="mod_select_links">');
modlist.hide();
modlist.currentlyshown = false;
links.appendChild(modlist);
for (var mod in modnames) {
// Only include actual values from the list.
if (!modnames.hasOwnProperty(mod)) {
continue;
}
html = html_generator('include_setting section_level', 'mod_' + mod, modnames[mod]);
if (withuserdata) {
html += html_generator('normal_setting', 'userdata-mod_' + mod, modnames[mod]);
}
var modlinks = Y.Node.create(
'<div class="grouped_settings section_level">' + html + '</div>');
modlist.appendChild(modlinks);
initlinks(modlinks, mod);
}
// Toggles the display of the hidden module select all/none links.
var toggletypes = function() {
// Change text of type toggle link.
var link = Y.one('#backup-bytype');
if (modlist.currentlyshown) {
link.setHTML(M.util.get_string('showtypes', 'backup'));
} else {
link.setHTML(M.util.get_string('hidetypes', 'backup'));
}
// The link has now been toggled (from show to hide, or vice-versa).
modlist.currentlyshown = !modlist.currentlyshown;
// Either hide or show the links.
var animcfg = {node: modlist, duration: 0.2},
anim;
if (modlist.currentlyshown) {
// Animate reveal of the module links.
modlist.show();
animcfg.to = {maxHeight: modlist.get('clientHeight') + 'px'};
modlist.setStyle('maxHeight', '0px');
anim = new Y.Anim(animcfg);
anim.on('end', function() {
modlist.setStyle('maxHeight', 'none');
});
anim.run();
} else {
// Animate hide of the module links.
animcfg.to = {maxHeight: '0px'};
modlist.setStyle('maxHeight', modlist.get('clientHeight') + 'px');
anim = new Y.Anim(animcfg);
anim.on('end', function() {
modlist.hide();
modlist.setStyle('maxHeight', 'none');
});
anim.run();
}
};
Y.one('#backup-bytype').on('click', function(e) {
e.preventDefault();
toggletypes();
});
Y.one('#backup-all-included').on('click', function(e) {
helper(e, true, '_included');
});
Y.one('#backup-none-included').on('click', function(e) {
helper(e, false, '_included');
});
if (withuserdata) {
Y.one('#backup-all-userdata').on('click', function(e) {
helper(e, true, withuserdata);
});
Y.one('#backup-none-userdata').on('click', function(e) {
helper(e, false, withuserdata);
});
}
};
@@ -0,0 +1,10 @@
{
"moodle-backup-backupselectall": {
"requires": [
"node",
"event",
"node-event-simulate",
"anim"
]
}
}