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
@@ -0,0 +1,43 @@
<?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/>.
/**
* @package moodlecore
* @subpackage backup-plan
* @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Abstract class defining the needed stuff to execute code on backup
*
* TODO: Finish phpdocs
*/
abstract class backup_execution_step extends backup_step {
public function execute() {
// Simple, for now
return $this->define_execution();
}
// Protected API starts here
/**
* Function that will contain all the code to be executed
*/
abstract protected function define_execution();
}
+174
View File
@@ -0,0 +1,174 @@
<?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/>.
/**
* @package moodlecore
* @subpackage backup-plan
* @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Implementable class defining the needed stuf for one backup plan
*
* TODO: Finish phpdocs
*/
class backup_plan extends base_plan implements loggable {
protected $controller; // The backup controller building/executing this plan
protected $basepath; // Fullpath to dir where backup is created
protected $excludingdactivities;
/**
* The role ids to keep in a copy operation.
* @var array
*/
protected $keptroles = array();
/**
* Constructor - instantiates one object of this class
*/
public function __construct($controller) {
if (! $controller instanceof backup_controller) {
throw new backup_plan_exception('wrong_backup_controller_specified');
}
$backuptempdir = make_backup_temp_directory('');
$this->controller = $controller;
$this->basepath = $backuptempdir . '/' . $controller->get_backupid();
$this->excludingdactivities = false;
parent::__construct('backup_plan');
}
/**
* Destroy all circular references. It helps PHP 5.2 a lot!
*/
public function destroy() {
// No need to destroy anything recursively here, direct reset
$this->controller = null;
// Delegate to base plan the rest
parent::destroy();
}
public function build() {
backup_factory::build_plan($this->controller); // Dispatch to correct format
$this->built = true;
}
public function get_backupid() {
return $this->controller->get_backupid();
}
public function get_type() {
return $this->controller->get_type();
}
public function get_mode() {
return $this->controller->get_mode();
}
public function get_courseid() {
return $this->controller->get_courseid();
}
public function get_basepath() {
return $this->basepath;
}
public function get_logger() {
return $this->controller->get_logger();
}
/**
* Gets the progress reporter, which can be used to report progress within
* the backup or restore process.
*
* @return \core\progress\base Progress reporting object
*/
public function get_progress() {
return $this->controller->get_progress();
}
public function is_excluding_activities() {
return $this->excludingdactivities;
}
public function set_excluding_activities() {
$this->excludingdactivities = true;
}
/**
* Sets the user roles that should be kept in the destination course
* for a course copy operation.
*
* @param array $roleids
*/
public function set_kept_roles(array $roleids): void {
$this->keptroles = $roleids;
}
/**
* Get the user roles that should be kept in the destination course
* for a course copy operation.
*
* @return array
*/
public function get_kept_roles(): array {
return $this->keptroles;
}
public function log($message, $level, $a = null, $depth = null, $display = false) {
backup_helper::log($message, $level, $a, $depth, $display, $this->get_logger());
}
/**
* Function responsible for executing the tasks of any plan
*/
public function execute() {
if ($this->controller->get_status() != backup::STATUS_AWAITING) {
throw new backup_controller_exception('backup_not_executable_awaiting_required', $this->controller->get_status());
}
$this->controller->set_status(backup::STATUS_EXECUTING);
parent::execute();
$this->controller->set_status(backup::STATUS_FINISHED_OK);
if ($this->controller->get_type() === backup::TYPE_1COURSE) {
// Trigger a course_backup_created event.
$otherarray = array('format' => $this->controller->get_format(),
'mode' => $this->controller->get_mode(),
'interactive' => $this->controller->get_interactive(),
'type' => $this->controller->get_type(),
'backupid' => $this->controller->get_backupid()
);
$event = \core\event\course_backup_created::create(array(
'objectid' => $this->get_courseid(),
'context' => context_course::instance($this->get_courseid()),
'other' => $otherarray
));
$event->trigger();
}
}
}
/*
* Exception class used by all the @backup_plan stuff
*/
class backup_plan_exception extends base_plan_exception {
public function __construct($errorcode, $a=NULL, $debuginfo=null) {
parent::__construct($errorcode, $a, $debuginfo);
}
}
+58
View File
@@ -0,0 +1,58 @@
<?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/>.
/**
* @package moodlecore
* @subpackage backup-plan
* @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Abstract class defining the needed stuf for one backup step
*
* TODO: Finish phpdocs
*/
abstract class backup_step extends base_step {
/**
* Constructor - instantiates one object of this class
*/
public function __construct($name, $task = null) {
if (!is_null($task) && !($task instanceof backup_task)) {
throw new backup_step_exception('wrong_backup_task_specified');
}
parent::__construct($name, $task);
}
protected function get_backupid() {
if (is_null($this->task)) {
throw new backup_step_exception('not_specified_backup_task');
}
return $this->task->get_backupid();
}
}
/*
* Exception class used by all the @backup_step stuff
*/
class backup_step_exception extends base_step_exception {
public function __construct($errorcode, $a=NULL, $debuginfo=null) {
parent::__construct($errorcode, $a, $debuginfo);
}
}
@@ -0,0 +1,270 @@
<?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/>.
/**
* @package moodlecore
* @subpackage backup-plan
* @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Abstract class defining the needed stuff to backup one @backup_structure
*
* TODO: Finish phpdocs
*/
abstract class backup_structure_step extends backup_step {
/**
* Name of the file to be generated
* @var string
*/
protected $filename;
/**
* xml content transformer being used (need it here, apart from xml_writer,
* thanks to serialized data to process - say thanks to blocks!)
* @var backup_xml_transformer|null
*/
protected $contenttransformer;
/**
* Constructor - instantiates one object of this class
*/
public function __construct($name, $filename, $task = null) {
if (!is_null($task) && !($task instanceof backup_task)) {
throw new backup_step_exception('wrong_backup_task_specified');
}
$this->filename = $filename;
$this->contenttransformer = null;
parent::__construct($name, $task);
}
public function execute() {
if (!$this->execute_condition()) { // Check any condition to execute this
return;
}
$fullpath = $this->task->get_taskbasepath();
// We MUST have one fullpath here, else, error
if (empty($fullpath)) {
throw new backup_step_exception('backup_structure_step_undefined_fullpath');
}
// Append the filename to the fullpath
$fullpath = rtrim($fullpath, '/') . '/' . $this->filename;
// Create output, transformer, writer, processor
$xo = new file_xml_output($fullpath);
$xt = null;
if (class_exists('backup_xml_transformer')) {
$xt = new backup_xml_transformer($this->get_courseid());
$this->contenttransformer = $xt; // Save the reference to the transformer
// as far as we are going to need it out
// from xml_writer (blame serialized data!)
}
$xw = new xml_writer($xo, $xt);
$progress = $this->task->get_progress();
$progress->start_progress($this->get_name());
$pr = new backup_structure_processor($xw, $progress);
// Set processor variables from settings
foreach ($this->get_settings() as $setting) {
$pr->set_var($setting->get_name(), $setting->get_value());
}
// Add backupid as one more var for processor
$pr->set_var(backup::VAR_BACKUPID, $this->get_backupid());
// Get structure definition
$structure = $this->define_structure();
if (! $structure instanceof backup_nested_element) {
throw new backup_step_exception('backup_structure_step_wrong_structure');
}
// Start writer
$xw->start();
// Process structure definition
$structure->process($pr);
// Get the results from the nested elements
$results = $structure->get_results();
// Get the log messages to append to the log
$logs = $structure->get_logs();
foreach ($logs as $log) {
$this->log($log->message, $log->level, $log->a, $log->depth, $log->display);
}
// Close everything
$xw->stop();
$progress->end_progress();
// Destroy the structure. It helps PHP 5.2 memory a lot!
$structure->destroy();
return $results;
}
/**
* As far as backup structure steps are implementing backup_plugin stuff, they need to
* have the parent task available for wrapping purposes (get course/context....)
*/
public function get_task() {
return $this->task;
}
// Protected API starts here
/**
* Add plugin structure to any element in the structure backup tree
*
* @param string $plugintype type of plugin as defined by core_component::get_plugin_types()
* @param backup_nested_element $element element in the structure backup tree that
* we are going to add plugin information to
* @param bool $multiple to define if multiple plugins can produce information
* for each instance of $element (true) or no (false)
*/
protected function add_plugin_structure($plugintype, $element, $multiple) {
global $CFG;
// Check the requested plugintype is a valid one
if (!array_key_exists($plugintype, core_component::get_plugin_types($plugintype))) {
throw new backup_step_exception('incorrect_plugin_type', $plugintype);
}
// Arrived here, plugin is correct, let's create the optigroup
$optigroupname = $plugintype . '_' . $element->get_name() . '_plugin';
$optigroup = new backup_optigroup($optigroupname, null, $multiple);
$element->add_child($optigroup); // Add optigroup to stay connected since beginning
// Get all the optigroup_elements, looking across all the plugin dirs
$pluginsdirs = core_component::get_plugin_list($plugintype);
foreach ($pluginsdirs as $name => $plugindir) {
$classname = 'backup_' . $plugintype . '_' . $name . '_plugin';
$backupfile = $plugindir . '/backup/moodle2/' . $classname . '.class.php';
if (file_exists($backupfile)) {
require_once($backupfile);
$backupplugin = new $classname($plugintype, $name, $optigroup, $this);
// Add plugin returned structure to optigroup
$backupplugin->define_plugin_structure($element->get_name());
}
}
}
/**
* Add subplugin structure for a given plugin to any element in the structure backup tree.
*
* This method allows the injection of subplugins (of a specified plugin) data to any
* element in any backup structure.
*
* NOTE: Initially subplugins were only available for activities (mod), so only the
* {@link backup_activity_structure_step} class had support for them, always
* looking for /mod/modulenanme subplugins. This new method is a generalization of the
* existing one for activities, supporting all subplugins injecting information everywhere.
*
* @param string $subplugintype type of subplugin as defined in plugin's db/subplugins.json.
* @param backup_nested_element $element element in the backup tree (anywhere) that
* we are going to add subplugin information to.
* @param bool $multiple to define if multiple subplugins can produce information
* for each instance of $element (true) or no (false).
* @param string $plugintype type of the plugin.
* @param string $pluginname name of the plugin.
* @return void
*/
protected function add_subplugin_structure($subplugintype, $element, $multiple, $plugintype = null, $pluginname = null) {
global $CFG;
// This global declaration is required, because where we do require_once($backupfile);
// That file may in turn try to do require_once($CFG->dirroot ...).
// That worked in the past, we should keep it working.
// Verify if this is a BC call for an activity backup. See NOTE above for this special case.
if ($plugintype === null and $pluginname === null) {
$plugintype = 'mod';
$pluginname = $this->task->get_modulename();
// TODO: Once all the calls have been changed to add both not null plugintype and pluginname, add a debugging here.
}
// Check the requested plugintype is a valid one.
if (!array_key_exists($plugintype, core_component::get_plugin_types())) {
throw new backup_step_exception('incorrect_plugin_type', $plugintype);
}
// Check the requested pluginname, for the specified plugintype, is a valid one.
if (!array_key_exists($pluginname, core_component::get_plugin_list($plugintype))) {
throw new backup_step_exception('incorrect_plugin_name', array($plugintype, $pluginname));
}
// Check the requested subplugintype is a valid one.
$subplugins = core_component::get_subplugins("{$plugintype}_{$pluginname}");
if (null === $subplugins) {
throw new backup_step_exception('plugin_missing_subplugins_configuration', [$plugintype, $pluginname]);
}
if (!array_key_exists($subplugintype, $subplugins)) {
throw new backup_step_exception('incorrect_subplugin_type', $subplugintype);
}
// Arrived here, subplugin is correct, let's create the optigroup.
$optigroupname = $subplugintype . '_' . $element->get_name() . '_subplugin';
$optigroup = new backup_optigroup($optigroupname, null, $multiple);
$element->add_child($optigroup); // Add optigroup to stay connected since beginning.
// Every subplugin optionally can have a common/parent subplugin
// class for shared stuff.
$parentclass = 'backup_' . $plugintype . '_' . $pluginname . '_' . $subplugintype . '_subplugin';
$parentfile = core_component::get_component_directory($plugintype . '_' . $pluginname) .
'/backup/moodle2/' . $parentclass . '.class.php';
if (file_exists($parentfile)) {
require_once($parentfile);
}
// Get all the optigroup_elements, looking over all the subplugin dirs.
$subpluginsdirs = core_component::get_plugin_list($subplugintype);
foreach ($subpluginsdirs as $name => $subpluginsdir) {
$classname = 'backup_' . $subplugintype . '_' . $name . '_subplugin';
$backupfile = $subpluginsdir . '/backup/moodle2/' . $classname . '.class.php';
if (file_exists($backupfile)) {
require_once($backupfile);
$backupsubplugin = new $classname($subplugintype, $name, $optigroup, $this);
// Add subplugin returned structure to optigroup.
$backupsubplugin->define_subplugin_structure($element->get_name());
}
}
}
/**
* To conditionally decide if one step will be executed or no
*
* For steps needing to be executed conditionally, based in dynamic
* conditions (at execution time vs at declaration time) you must
* override this function. It will return true if the step must be
* executed and false if not
*/
protected function execute_condition() {
return true;
}
/**
* Define the structure to be processed by this backup step.
*
* @return backup_nested_element
*/
abstract protected function define_structure();
}
+69
View File
@@ -0,0 +1,69 @@
<?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/>.
/**
* @package moodlecore
* @subpackage backup-plan
* @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Abstract class defining the needed stuf for one backup task (a collection of steps)
*
* TODO: Finish phpdocs
*/
abstract class backup_task extends base_task {
/**
* Constructor - instantiates one object of this class
*/
public function __construct($name, $plan = null) {
if (!is_null($plan) && !($plan instanceof backup_plan)) {
throw new backup_task_exception('wrong_backup_plan_specified');
}
parent::__construct($name, $plan);
}
public function get_backupid() {
return $this->plan->get_backupid();
}
public function is_excluding_activities() {
return $this->plan->is_excluding_activities();
}
/**
* Get the user roles that should be kept in the destination course
* for a course copy operation.
*
* @return array
*/
public function get_kept_roles(): array {
return $this->plan->get_kept_roles();
}
}
/*
* Exception class used by all the @backup_task stuff
*/
class backup_task_exception extends base_task_exception {
public function __construct($errorcode, $a=NULL, $debuginfo=null) {
parent::__construct($errorcode, $a, $debuginfo);
}
}
+232
View File
@@ -0,0 +1,232 @@
<?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/>.
/**
* @package moodlecore
* @subpackage backup-plan
* @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Abstract class defining the basis for one execution (backup/restore) plan
*
* TODO: Finish phpdocs
*/
abstract class base_plan implements checksumable, executable {
protected $name; // One simple name for identification purposes
protected $settings; // One array of (accumulated from tasks) base_setting elements
protected $tasks; // One array of base_task elements
protected $results; // One array of results received from tasks
protected $built; // Flag to know if one plan has been built
/**
* Constructor - instantiates one object of this class
*/
public function __construct($name) {
$this->name = $name;
$this->settings = array();
$this->tasks = array();
$this->results = array();
$this->built = false;
}
public function get_name() {
return $this->name;
}
public function add_task($task) {
if (! $task instanceof base_task) {
throw new base_plan_exception('wrong_base_task_specified');
}
$this->tasks[] = $task;
// link the task with the plan
$task->set_plan($this);
// Append task settings to plan array, if not present, for comodity
foreach ($task->get_settings() as $key => $setting) {
// Check if there is already a setting for this name.
$name = $setting->get_name();
if (!isset($this->settings[$name])) {
// There is no setting, so add it.
$this->settings[$name] = $setting;
} else if ($this->settings[$name] != $setting) {
// If the setting already exists AND it is not the same setting,
// then throw an error. (I.e. you're allowed to add the same
// setting twice, but cannot add two different ones with same
// name.)
throw new base_plan_exception('multiple_settings_by_name_found', $name);
}
}
}
public function get_tasks() {
return $this->tasks;
}
/**
* Add the passed info to the plan results
*
* At the moment we expect an associative array structure to be merged into
* the current results. In the future, some sort of base_result class may
* be introduced.
*
* @param array $result associative array describing a result of a task/step
*/
public function add_result($result) {
if (!is_array($result)) {
throw new coding_exception('Associative array is expected as a parameter of add_result()');
}
$this->results = array_merge($this->results, $result);
}
/**
* Return the results collected via {@link self::add_result()} method
*
* @return array
*/
public function get_results() {
return $this->results;
}
public function get_settings() {
return $this->settings;
}
/**
* return one setting by name, useful to request root/course settings
* that are, by definition, unique by name.
*
* @param string $name name of the setting
* @return base_setting
* @throws base_plan_exception if setting name is not found.
*/
public function get_setting($name) {
$result = null;
if (isset($this->settings[$name])) {
$result = $this->settings[$name];
} else {
throw new base_plan_exception('setting_by_name_not_found', $name);
}
return $result;
}
/**
* For debug only. Get a simple test display of all the settings.
*
* @return string
*/
public function debug_display_all_settings_values(): string {
$result = '';
foreach ($this->settings as $name => $setting) {
$result .= $name . ': ' . $setting->get_value() . "\n";
}
return $result;
}
/**
* Wrapper over @get_setting() that returns if the requested setting exists or no
*/
public function setting_exists($name) {
try {
$this->get_setting($name);
return true;
} catch (base_plan_exception $e) {
// Nothing to do
}
return false;
}
/**
* Function responsible for building the tasks of any plan
* with their corresponding settings
* (must set the $built property to true)
*/
abstract public function build();
public function is_checksum_correct($checksum) {
return $this->calculate_checksum() === $checksum;
}
public function calculate_checksum() {
// Let's do it using name and tasks (settings are part of tasks)
return md5($this->name . '-' . backup_general_helper::array_checksum_recursive($this->tasks));
}
/**
* Function responsible for executing the tasks of any plan
*/
public function execute() {
if (!$this->built) {
throw new base_plan_exception('base_plan_not_built');
}
// Calculate the total weight of all tasks and start progress tracking.
$progress = $this->get_progress();
$totalweight = 0;
foreach ($this->tasks as $task) {
$totalweight += $task->get_weight();
}
$progress->start_progress($this->get_name(), $totalweight);
// Build and execute all tasks.
foreach ($this->tasks as $task) {
$task->build();
$task->execute();
}
// Finish progress tracking.
$progress->end_progress();
}
/**
* Gets the progress reporter, which can be used to report progress within
* the backup or restore process.
*
* @return \core\progress\base Progress reporting object
*/
abstract public function get_progress();
/**
* Destroy all circular references. It helps PHP 5.2 a lot!
*/
public function destroy() {
// Before reseting anything, call destroy recursively
foreach ($this->tasks as $task) {
$task->destroy();
}
foreach ($this->settings as $setting) {
$setting->destroy();
}
// Everything has been destroyed recursively, now we can reset safely
$this->tasks = array();
$this->settings = array();
}
}
/*
* Exception class used by all the @base_plan stuff
*/
class base_plan_exception extends moodle_exception {
public function __construct($errorcode, $a=NULL, $debuginfo=null) {
parent::__construct($errorcode, '', '', $a, $debuginfo);
}
}
+132
View File
@@ -0,0 +1,132 @@
<?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/>.
/**
* @package moodlecore
* @subpackage backup-plan
* @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Abstract class defining the basis for one execution (backup/restore) step
*
* TODO: Finish phpdocs
*/
abstract class base_step implements executable, loggable {
/** @var string One simple name for identification purposes */
protected $name;
/** @var base_task|null Task this is part of */
protected $task;
/**
* Constructor - instantiates one object of this class
*/
public function __construct($name, $task = null) {
if (!is_null($task) && !($task instanceof base_task)) {
throw new base_step_exception('wrong_base_task_specified');
}
$this->name = $name;
$this->task = $task;
if (!is_null($task)) { // Add the step to the task if specified
$task->add_step($this);
}
}
public function get_name() {
return $this->name;
}
public function set_task($task) {
if (! $task instanceof base_task) {
throw new base_step_exception('wrong_base_task_specified');
}
$this->task = $task;
}
/**
* Destroy all circular references. It helps PHP 5.2 a lot!
*/
public function destroy() {
// No need to destroy anything recursively here, direct reset
$this->task = null;
}
public function log($message, $level, $a = null, $depth = null, $display = false) {
if (is_null($this->task)) {
throw new base_step_exception('not_specified_base_task');
}
backup_helper::log($message, $level, $a, $depth, $display, $this->get_logger());
}
/// Protected API starts here
protected function get_settings() {
if (is_null($this->task)) {
throw new base_step_exception('not_specified_base_task');
}
return $this->task->get_settings();
}
protected function get_setting($name) {
if (is_null($this->task)) {
throw new base_step_exception('not_specified_base_task');
}
return $this->task->get_setting($name);
}
protected function setting_exists($name) {
if (is_null($this->task)) {
throw new base_step_exception('not_specified_base_task');
}
return $this->task->setting_exists($name);
}
protected function get_setting_value($name) {
if (is_null($this->task)) {
throw new base_step_exception('not_specified_base_task');
}
return $this->task->get_setting_value($name);
}
protected function get_courseid() {
if (is_null($this->task)) {
throw new base_step_exception('not_specified_base_task');
}
return $this->task->get_courseid();
}
protected function get_basepath() {
return $this->task->get_basepath();
}
protected function get_logger() {
return $this->task->get_logger();
}
}
/*
* Exception class used by all the @base_step stuff
*/
class base_step_exception extends moodle_exception {
public function __construct($errorcode, $a=NULL, $debuginfo=null) {
parent::__construct($errorcode, '', '', $a, $debuginfo);
}
}
+285
View File
@@ -0,0 +1,285 @@
<?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/>.
/**
* @package moodlecore
* @subpackage backup-plan
* @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Abstract class defining the basis for one execution (backup/restore) task
*
* TODO: Finish phpdocs
*/
abstract class base_task implements checksumable, executable, loggable {
/** @var string */
protected $name; // One simple name for identification purposes
/** @var backup_plan|restore_plan */
protected $plan; // Plan this is part of
/** @var base_setting[] */
protected $settings; // One array of base_setting elements to define this task
/** @var base_step[] */
protected $steps; // One array of base_step elements
/** @var bool */
protected $built; // Flag to know if one task has been built
/** @var bool */
protected $executed; // Flag to know if one task has been executed
/**
* Constructor - instantiates one object of this class
*/
public function __construct($name, $plan = null) {
if (!is_null($plan) && !($plan instanceof base_plan)) {
throw new base_task_exception('wrong_base_plan_specified');
}
$this->name = $name;
$this->plan = $plan;
$this->settings = array();
$this->steps = array();
$this->built = false;
$this->executed = false;
if (!is_null($plan)) { // Add the task to the plan if specified
$plan->add_task($this);
}
}
public function get_name() {
return $this->name;
}
public function get_steps() {
return $this->steps;
}
public function get_settings() {
return $this->settings;
}
/**
* Returns the weight of this task, an approximation of the amount of time
* it will take. By default this value is 1. It can be increased for longer
* tasks.
*
* @return int Weight
*/
public function get_weight() {
return 1;
}
public function get_setting($name) {
// First look in task settings
$result = null;
foreach ($this->settings as $key => $setting) {
if ($setting->get_name() == $name) {
if ($result != null) {
throw new base_task_exception('multiple_settings_by_name_found', $name);
} else {
$result = $setting;
}
}
}
if ($result) {
return $result;
} else {
// Fallback to plan settings
return $this->plan->get_setting($name);
}
}
public function setting_exists($name) {
return $this->plan->setting_exists($name);
}
public function get_setting_value($name) {
return $this->get_setting($name)->get_value();
}
public function get_courseid() {
return $this->plan->get_courseid();
}
public function get_basepath() {
return $this->plan->get_basepath();
}
public function get_taskbasepath() {
return $this->get_basepath();
}
public function get_logger() {
return $this->plan->get_logger();
}
/**
* Gets the progress reporter, which can be used to report progress within
* the backup or restore process.
*
* @return \core\progress\base Progress reporting object
*/
public function get_progress() {
return $this->plan->get_progress();
}
public function log($message, $level, $a = null, $depth = null, $display = false) {
backup_helper::log($message, $level, $a, $depth, $display, $this->get_logger());
}
public function add_step($step) {
if (! $step instanceof base_step) {
throw new base_task_exception('wrong_base_step_specified');
}
// link the step with the task
$step->set_task($this);
$this->steps[] = $step;
}
public function set_plan($plan) {
if (! $plan instanceof base_plan) {
throw new base_task_exception('wrong_base_plan_specified');
}
$this->plan = $plan;
$this->define_settings(); // Settings are defined when plan & task are linked
}
/**
* Function responsible for building the steps of any task
* (must set the $built property to true)
*/
abstract public function build();
/**
* Function responsible for executing the steps of any task
* (setting the $executed property to true)
*/
public function execute() {
if (!$this->built) {
throw new base_task_exception('base_task_not_built', $this->name);
}
if ($this->executed) {
throw new base_task_exception('base_task_already_executed', $this->name);
}
// Starts progress based on the weight of this task and number of steps.
$progress = $this->get_progress();
$progress->start_progress($this->get_name(), count($this->steps), $this->get_weight());
$done = 0;
// Execute all steps.
foreach ($this->steps as $step) {
$result = $step->execute();
// If step returns array, it will be forwarded to plan
// (TODO: shouldn't be array but proper result object)
if (is_array($result) and !empty($result)) {
$this->add_result($result);
}
$done++;
$progress->progress($done);
}
// Mark as executed if any step has been executed
if (!empty($this->steps)) {
$this->executed = true;
}
// Finish progress for this task.
$progress->end_progress();
}
/**
* Destroy all circular references. It helps PHP 5.2 a lot!
*/
public function destroy() {
// Before reseting anything, call destroy recursively
foreach ($this->steps as $step) {
$step->destroy();
}
foreach ($this->settings as $setting) {
$setting->destroy();
}
// Everything has been destroyed recursively, now we can reset safely
$this->steps = array();
$this->settings = array();
$this->plan = null;
}
public function is_checksum_correct($checksum) {
return $this->calculate_checksum() === $checksum;
}
public function calculate_checksum() {
// Let's do it using name and settings and steps
return md5($this->name . '-' .
backup_general_helper::array_checksum_recursive($this->settings) .
backup_general_helper::array_checksum_recursive($this->steps));
}
/**
* Add the given info to the current plan's results.
*
* @see base_plan::add_result()
* @param array $result associative array describing a result of a task/step
*/
public function add_result($result) {
if (!is_null($this->plan)) {
$this->plan->add_result($result);
} else {
debugging('Attempting to add a result of a task not binded with a plan', DEBUG_DEVELOPER);
}
}
/**
* Return the current plan's results
*
* @return array|null
*/
public function get_results() {
if (!is_null($this->plan)) {
return $this->plan->get_results();
} else {
debugging('Attempting to get results of a task not binded with a plan', DEBUG_DEVELOPER);
return null;
}
}
// Protected API starts here
/**
* This function is invoked on activity creation in order to add all the settings
* that are associated with one task. The function will, directly, inject the settings
* in the task.
*/
abstract protected function define_settings();
protected function add_setting($setting) {
if (! $setting instanceof base_setting) {
throw new base_setting_exception('wrong_base_setting_specified');
}
$this->settings[] = $setting;
}
}
/*
* Exception class used by all the @base_task stuff
*/
class base_task_exception extends moodle_exception {
public function __construct($errorcode, $a=NULL, $debuginfo=null) {
parent::__construct($errorcode, '', '', $a, $debuginfo);
}
}
@@ -0,0 +1,43 @@
<?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/>.
/**
* @package moodlecore
* @subpackage backup-plan
* @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Abstract class defining the needed stuff to execute code on restore
*
* TODO: Finish phpdocs
*/
abstract class restore_execution_step extends restore_step {
public function execute() {
// Simple, for now
return $this->define_execution();
}
// Protected API starts here
/**
* Function that will contain all the code to be executed
*/
abstract protected function define_execution();
}
+249
View File
@@ -0,0 +1,249 @@
<?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/>.
/**
* @package moodlecore
* @subpackage backup-plan
* @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Implementable class defining the needed stuf for one restore plan
*
* TODO: Finish phpdocs
*/
class restore_plan extends base_plan implements loggable {
/**
*
* @var restore_controller
*/
protected $controller; // The restore controller building/executing this plan
protected $basepath; // Fullpath to dir where backup is available
protected $preloaded; // When executing the plan, do we have preloaded (from checks) info
protected $decoder; // restore_decode_processor in charge of decoding all the interlinks
protected $missingmodules; // to flag if restore has detected some missing module
protected $excludingdactivities; // to flag if restore settings are excluding any activity
/**
* Constructor - instantiates one object of this class
*/
public function __construct($controller) {
global $CFG;
if (! $controller instanceof restore_controller) {
throw new restore_plan_exception('wrong_restore_controller_specified');
}
$backuptempdir = make_backup_temp_directory('');
$this->controller = $controller;
$this->basepath = $backuptempdir . '/' . $controller->get_tempdir();
$this->preloaded = false;
$this->decoder = new restore_decode_processor($this->get_restoreid(), $this->get_info()->original_wwwroot, $CFG->wwwroot);
$this->missingmodules = false;
$this->excludingdactivities = false;
parent::__construct('restore_plan');
}
/**
* Destroy all circular references. It helps PHP 5.2 a lot!
*/
public function destroy() {
// No need to destroy anything recursively here, direct reset
$this->controller = null;
// Delegate to base plan the rest
parent::destroy();
}
public function build() {
restore_plan_builder::build_plan($this->controller); // We are moodle2 always, go straight to builder
$this->built = true;
}
public function get_restoreid() {
return $this->controller->get_restoreid();
}
public function get_courseid() {
return $this->controller->get_courseid();
}
public function get_mode() {
return $this->controller->get_mode();
}
public function get_basepath() {
return $this->basepath;
}
public function get_logger() {
return $this->controller->get_logger();
}
/**
* Gets the progress reporter, which can be used to report progress within
* the backup or restore process.
*
* @return \core\progress\base Progress reporting object
*/
public function get_progress() {
return $this->controller->get_progress();
}
public function get_info() {
return $this->controller->get_info();
}
public function get_target() {
return $this->controller->get_target();
}
public function get_userid() {
return $this->controller->get_userid();
}
public function get_decoder() {
return $this->decoder;
}
public function is_samesite() {
return $this->controller->is_samesite();
}
public function is_missing_modules() {
return $this->missingmodules;
}
public function is_excluding_activities() {
return $this->excludingdactivities;
}
public function set_preloaded_information() {
$this->preloaded = true;
}
public function get_preloaded_information() {
return $this->preloaded;
}
public function get_tempdir() {
return $this->controller->get_tempdir();
}
public function set_missing_modules() {
$this->missingmodules = true;
}
public function set_excluding_activities() {
$this->excludingdactivities = true;
}
public function log($message, $level, $a = null, $depth = null, $display = false) {
backup_helper::log($message, $level, $a, $depth, $display, $this->get_logger());
}
/**
* Function responsible for executing the tasks of any plan
*/
public function execute() {
if ($this->controller->get_status() != backup::STATUS_AWAITING) {
throw new restore_controller_exception('restore_not_executable_awaiting_required', $this->controller->get_status());
}
$this->controller->set_status(backup::STATUS_EXECUTING);
parent::execute();
$this->controller->set_status(backup::STATUS_FINISHED_OK);
// Check if we are restoring a course.
if ($this->controller->get_type() === backup::TYPE_1COURSE) {
// Check to see if we are on the same site to pass original course info.
$issamesite = $this->controller->is_samesite();
$otherarray = array('type' => $this->controller->get_type(),
'target' => $this->controller->get_target(),
'mode' => $this->controller->get_mode(),
'operation' => $this->controller->get_operation(),
'samesite' => $issamesite
);
if ($this->controller->is_samesite()) {
$otherarray['originalcourseid'] = $this->controller->get_info()->original_course_id;
}
// Trigger a course restored event.
$event = \core\event\course_restored::create(array(
'objectid' => $this->get_courseid(),
'userid' => $this->get_userid(),
'context' => context_course::instance($this->get_courseid()),
'other' => $otherarray
));
$event->trigger();
}
}
/**
* Execute the after_restore methods of all the executed tasks in the plan
*/
public function execute_after_restore() {
// Simply iterate over each task in the plan and delegate to them the execution
$progress = $this->get_progress();
$progress->start_progress($this->get_name() .
': executing execute_after_restore for all tasks', count($this->tasks));
/** @var base_task $task */
foreach ($this->tasks as $task) {
$task->execute_after_restore();
$progress->increment_progress();
}
$progress->end_progress();
}
/**
* Compares the provided moodle version with the one the backup was taken from.
*
* @param int $version Moodle version number (YYYYMMDD or YYYYMMDDXX)
* @param string $operator Operator to compare the provided version to the backup version. {@see version_compare()}
* @return bool True if the comparison passes.
*/
public function backup_version_compare(int $version, string $operator): bool {
preg_match('/(\d{' . strlen($version) . '})/', $this->get_info()->moodle_version, $matches);
$backupbuild = (int)$matches[1];
return version_compare($backupbuild, $version, $operator);
}
/**
* Compares the provided moodle release with the one the backup was taken from.
*
* @param string $release Moodle release (X.Y or X.Y.Z)
* @param string $operator Operator to compare the provided release to the backup release. {@see version_compare()}
* @return bool True if the comparison passes.
*/
public function backup_release_compare(string $release, string $operator): bool {
return version_compare($this->get_info()->backup_release, $release, $operator);
}
}
/*
* Exception class used by all the @restore_plan stuff
*/
class restore_plan_exception extends base_plan_exception {
public function __construct($errorcode, $a=NULL, $debuginfo=null) {
parent::__construct($errorcode, $a, $debuginfo);
}
}
+165
View File
@@ -0,0 +1,165 @@
<?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/>.
/**
* @package moodlecore
* @subpackage backup-plan
* @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Abstract class defining the needed stuf for one restore step
*
* TODO: Finish phpdocs
*/
abstract class restore_step extends base_step {
/**
* Constructor - instantiates one object of this class
*/
public function __construct($name, $task = null) {
if (!is_null($task) && !($task instanceof restore_task)) {
throw new restore_step_exception('wrong_restore_task_specified');
}
parent::__construct($name, $task);
}
protected function get_restoreid() {
if (is_null($this->task)) {
throw new restore_step_exception('not_specified_restore_task');
}
return $this->task->get_restoreid();
}
/**
* Apply course startdate offset based in original course startdate and course_offset_startdate setting
* Note we are using one static cache here, but *by restoreid*, so it's ok for concurrence/multiple
* executions in the same request
*
* Note: The policy is to roll date only for configurations and not for user data. see MDL-9367.
*
* @param int $value Time value (seconds since epoch), or empty for nothing
* @return int Time value after applying the date offset, or empty for nothing
*/
public function apply_date_offset($value) {
// Empties don't offset - zeros (int and string), false and nulls return original value.
if (empty($value)) {
return $value;
}
static $cache = array();
// Lookup cache.
if (isset($cache[$this->get_restoreid()])) {
return $value + $cache[$this->get_restoreid()];
}
// No cache, let's calculate the offset.
$original = $this->task->get_info()->original_course_startdate;
$setting = 0;
if ($this->setting_exists('course_startdate')) { // Seting may not exist (MDL-25019).
$settingobject = $this->task->get_setting('course_startdate');
if (method_exists($settingobject, 'get_normalized_value')) {
$setting = $settingobject->get_normalized_value();
} else {
$setting = $settingobject->get_value();
}
}
if (empty($original) || empty($setting)) {
// Original course has not startdate or setting doesn't exist, offset = 0.
$cache[$this->get_restoreid()] = 0;
} else {
// Arrived here, let's calculate the real offset.
$cache[$this->get_restoreid()] = $setting - $original;
}
// Return the passed value with cached offset applied.
return $value + $cache[$this->get_restoreid()];
}
/**
* Returns symmetric-key AES-256 decryption of base64 encoded contents.
*
* This method is used in restore operations to decrypt contents encrypted with
* {@link encrypted_final_element} automatically decoding (base64) and decrypting
* contents using the key stored in backup_encryptkey config.
*
* Requires openssl, cipher availability, and key existence (backup
* automatically sets it if missing). Integrity is provided via HMAC.
*
* @param string $value {@link encrypted_final_element} value to decode and decrypt.
* @return string|null decoded and decrypted value or null if the operation can not be performed.
*/
public function decrypt($value) {
// No openssl available, skip this field completely.
if (!function_exists('openssl_encrypt')) {
return null;
}
// No hash available, skip this field completely.
if (!function_exists('hash_hmac')) {
return null;
}
// Cypher not available, skip this field completely.
if (!in_array(backup::CIPHER, openssl_get_cipher_methods())) {
return null;
}
// Get the decrypt key. Skip if missing.
$key = get_config('backup', 'backup_encryptkey');
if ($key === false) {
return null;
}
// And decode it.
$key = base64_decode($key);
// Arrived here, let's proceed with authentication (provides integrity).
$hmaclen = 32; // SHA256 is 32 bytes.
$ivlen = openssl_cipher_iv_length(backup::CIPHER);
list($hmac, $iv, $text) = array_values(unpack("a{$hmaclen}hmac/a{$ivlen}iv/a*text", base64_decode($value)));
// Verify HMAC matches expectations, skip if not (integrity failed).
if (!hash_equals($hmac, hash_hmac('sha256', $iv . $text, $key, true))) {
return null;
}
// Arrived here, integrity is ok, let's decrypt.
$result = openssl_decrypt($text, backup::CIPHER, $key, OPENSSL_RAW_DATA, $iv);
// For some reason decrypt failed (strange, HMAC check should have deteted it), skip this field completely.
if ($result === false) {
return null;
}
return $result;
}
}
/*
* Exception class used by all the @restore_step stuff
*/
class restore_step_exception extends base_step_exception {
public function __construct($errorcode, $a=NULL, $debuginfo=null) {
parent::__construct($errorcode, $a, $debuginfo);
}
}
@@ -0,0 +1,548 @@
<?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/>.
/**
* @package moodlecore
* @subpackage backup-plan
* @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Abstract class defining the needed stuff to restore one xml file
*
* TODO: Finish phpdocs
*/
abstract class restore_structure_step extends restore_step {
protected $filename; // Name of the file to be parsed
protected $contentprocessor; // xml parser processor being used
// (need it here, apart from parser
// thanks to serialized data to process -
// say thanks to blocks!)
protected $pathelements; // Array of pathelements to process
protected $elementsoldid; // Array to store last oldid used on each element
protected $elementsnewid; // Array to store last newid used on each element
protected $pathlock; // Path currently locking processing of children
const SKIP_ALL_CHILDREN = -991399; // To instruct the dispatcher about to ignore
// all children below path processor returning it
/**
* Constructor - instantiates one object of this class
*/
public function __construct($name, $filename, $task = null) {
if (!is_null($task) && !($task instanceof restore_task)) {
throw new restore_step_exception('wrong_restore_task_specified');
}
$this->filename = $filename;
$this->contentprocessor = null;
$this->pathelements = array();
$this->elementsoldid = array();
$this->elementsnewid = array();
$this->pathlock = null;
parent::__construct($name, $task);
}
final public function execute() {
if (!$this->execute_condition()) { // Check any condition to execute this
return;
}
$fullpath = $this->task->get_taskbasepath();
// We MUST have one fullpath here, else, error
if (empty($fullpath)) {
throw new restore_step_exception('restore_structure_step_undefined_fullpath');
}
// Append the filename to the fullpath
$fullpath = rtrim($fullpath, '/') . '/' . $this->filename;
// And it MUST exist
if (!file_exists($fullpath)) { // Shouldn't happen ever, but...
throw new restore_step_exception('missing_moodle_backup_xml_file', $fullpath);
}
// Get restore_path elements array adapting and preparing it for processing
$structure = $this->define_structure();
if (!is_array($structure)) {
throw new restore_step_exception('restore_step_structure_not_array', $this->get_name());
}
$this->prepare_pathelements($structure);
// Create parser and processor
$xmlparser = new progressive_parser();
$xmlparser->set_file($fullpath);
$xmlprocessor = new restore_structure_parser_processor($this->task->get_courseid(), $this);
$this->contentprocessor = $xmlprocessor; // Save the reference to the contentprocessor
// as far as we are going to need it out
// from parser (blame serialized data!)
$xmlparser->set_processor($xmlprocessor);
// Add pathelements to processor
foreach ($this->pathelements as $element) {
$xmlprocessor->add_path($element->get_path(), $element->is_grouped());
}
// Set up progress tracking.
$progress = $this->get_task()->get_progress();
$progress->start_progress($this->get_name(), \core\progress\base::INDETERMINATE);
$xmlparser->set_progress($progress);
// And process it, dispatch to target methods in step will start automatically
$xmlparser->process();
// Have finished, launch the after_execute method of all the processing objects
$this->launch_after_execute_methods();
$progress->end_progress();
}
/**
* Receive one chunk of information form the xml parser processor and
* dispatch it, following the naming rules
*/
final public function process($data) {
if (!array_key_exists($data['path'], $this->pathelements)) { // Incorrect path, must not happen
throw new restore_step_exception('restore_structure_step_missing_path', $data['path']);
}
$element = $this->pathelements[$data['path']];
$object = $element->get_processing_object();
$method = $element->get_processing_method();
$rdata = null;
if (empty($object)) { // No processing object defined
throw new restore_step_exception('restore_structure_step_missing_pobject', $object);
}
// Release the lock if we aren't anymore within children of it
if (!is_null($this->pathlock) and strpos($data['path'], $this->pathlock) === false) {
$this->pathlock = null;
}
if (is_null($this->pathlock)) { // Only dispatch if there isn't any lock
$rdata = $object->$method($data['tags']); // Dispatch to proper object/method
}
// If the dispatched method returns SKIP_ALL_CHILDREN, we grab current path in order to
// lock dispatching to any children
if ($rdata === self::SKIP_ALL_CHILDREN) {
// Check we haven't any previous lock
if (!is_null($this->pathlock)) {
throw new restore_step_exception('restore_structure_step_already_skipping', $data['path']);
}
// Set the lock
$this->pathlock = $data['path'] . '/'; // Lock everything below current path
// Continue with normal processing of return values
} else if ($rdata !== null) { // If the method has returned any info, set element data to it
$element->set_data($rdata);
} else { // Else, put the original parsed data
$element->set_data($data);
}
}
/**
* To send ids pairs to backup_ids_table and to store them into paths
*
* This method will send the given itemname and old/new ids to the
* backup_ids_temp table, and, at the same time, will save the new id
* into the corresponding restore_path_element for easier access
* by children. Also will inject the known old context id for the task
* in case it's going to be used for restoring files later
*/
public function set_mapping($itemname, $oldid, $newid, $restorefiles = false, $filesctxid = null, $parentid = null) {
if ($restorefiles && $parentid) {
throw new restore_step_exception('set_mapping_cannot_specify_both_restorefiles_and_parentitemid');
}
// If we haven't specified one context for the files, use the task one
if (is_null($filesctxid)) {
$parentitemid = $restorefiles ? $this->task->get_old_contextid() : null;
} else { // Use the specified one
$parentitemid = $restorefiles ? $filesctxid : null;
}
// We have passed one explicit parentid, apply it
$parentitemid = !is_null($parentid) ? $parentid : $parentitemid;
// Let's call the low level one
restore_dbops::set_backup_ids_record($this->get_restoreid(), $itemname, $oldid, $newid, $parentitemid);
// Now, if the itemname matches any pathelement->name, store the latest $newid
if (array_key_exists($itemname, $this->elementsoldid)) { // If present in $this->elementsoldid, is valid, put both ids
$this->elementsoldid[$itemname] = $oldid;
$this->elementsnewid[$itemname] = $newid;
}
}
/**
* Returns the latest (parent) old id mapped by one pathelement
*/
public function get_old_parentid($itemname) {
return array_key_exists($itemname, $this->elementsoldid) ? $this->elementsoldid[$itemname] : null;
}
/**
* Returns the latest (parent) new id mapped by one pathelement
*/
public function get_new_parentid($itemname) {
return array_key_exists($itemname, $this->elementsnewid) ? $this->elementsnewid[$itemname] : null;
}
/**
* Return the new id of a mapping for the given itemname
*
* @param string $itemname the type of item
* @param int $oldid the item ID from the backup
* @param mixed $ifnotfound what to return if $oldid wasnt found. Defaults to false
*/
public function get_mappingid($itemname, $oldid, $ifnotfound = false) {
$mapping = $this->get_mapping($itemname, $oldid);
return $mapping ? $mapping->newitemid : $ifnotfound;
}
/**
* Return the complete mapping from the given itemname, itemid
*/
public function get_mapping($itemname, $oldid) {
return restore_dbops::get_backup_ids_record($this->get_restoreid(), $itemname, $oldid);
}
/**
* Add all the existing file, given their component and filearea and one backup_ids itemname to match with
*/
public function add_related_files($component, $filearea, $mappingitemname, $filesctxid = null, $olditemid = null) {
// If the current progress object is set up and ready to receive
// indeterminate progress, then use it, otherwise don't. (This check is
// just in case this function is ever called from somewhere not within
// the execute() method here, which does set up progress like this.)
$progress = $this->get_task()->get_progress();
if (!$progress->is_in_progress_section() ||
$progress->get_current_max() !== \core\progress\base::INDETERMINATE) {
$progress = null;
}
$filesctxid = is_null($filesctxid) ? $this->task->get_old_contextid() : $filesctxid;
$results = restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), $component,
$filearea, $filesctxid, $this->task->get_userid(), $mappingitemname, $olditemid, null, false,
$progress);
$resultstoadd = array();
foreach ($results as $result) {
$this->log($result->message, $result->level);
$resultstoadd[$result->code] = true;
}
$this->task->add_result($resultstoadd);
}
/**
* As far as restore structure steps are implementing restore_plugin stuff, they need to
* have the parent task available for wrapping purposes (get course/context....)
* @return restore_task|null
*/
public function get_task() {
return $this->task;
}
// Protected API starts here
/**
* Add plugin structure to any element in the structure restore tree
*
* @param string $plugintype type of plugin as defined by core_component::get_plugin_types()
* @param restore_path_element $element element in the structure restore tree that
* we are going to add plugin information to
*/
protected function add_plugin_structure($plugintype, $element) {
global $CFG;
// Check the requested plugintype is a valid one
if (!array_key_exists($plugintype, core_component::get_plugin_types($plugintype))) {
throw new restore_step_exception('incorrect_plugin_type', $plugintype);
}
// Get all the restore path elements, looking across all the plugin dirs
$pluginsdirs = core_component::get_plugin_list($plugintype);
foreach ($pluginsdirs as $name => $pluginsdir) {
// We need to add also backup plugin classes on restore, they may contain
// some stuff used both in backup & restore
$backupclassname = 'backup_' . $plugintype . '_' . $name . '_plugin';
$backupfile = $pluginsdir . '/backup/moodle2/' . $backupclassname . '.class.php';
if (file_exists($backupfile)) {
require_once($backupfile);
}
// Now add restore plugin classes and prepare stuff
$restoreclassname = 'restore_' . $plugintype . '_' . $name . '_plugin';
$restorefile = $pluginsdir . '/backup/moodle2/' . $restoreclassname . '.class.php';
if (file_exists($restorefile)) {
require_once($restorefile);
$restoreplugin = new $restoreclassname($plugintype, $name, $this);
// Add plugin paths to the step
$this->prepare_pathelements($restoreplugin->define_plugin_structure($element));
}
}
}
/**
* Add subplugin structure for a given plugin to any element in the structure restore tree
*
* This method allows the injection of subplugins (of a specific plugin) parsing and proccessing
* to any element in the restore structure.
*
* NOTE: Initially subplugins were only available for activities (mod), so only the
* {@link restore_activity_structure_step} class had support for them, always
* looking for /mod/modulenanme subplugins. This new method is a generalization of the
* existing one for activities, supporting all subplugins injecting information everywhere.
*
* @param string $subplugintype type of subplugin as defined in plugin's db/subplugins.json.
* @param restore_path_element $element element in the structure restore tree that
* we are going to add subplugin information to.
* @param string $plugintype type of the plugin.
* @param string $pluginname name of the plugin.
* @return void
*/
protected function add_subplugin_structure($subplugintype, $element, $plugintype = null, $pluginname = null) {
global $CFG;
// This global declaration is required, because where we do require_once($backupfile);
// That file may in turn try to do require_once($CFG->dirroot ...).
// That worked in the past, we should keep it working.
// Verify if this is a BC call for an activity restore. See NOTE above for this special case.
if ($plugintype === null and $pluginname === null) {
$plugintype = 'mod';
$pluginname = $this->task->get_modulename();
// TODO: Once all the calls have been changed to add both not null plugintype and pluginname, add a debugging here.
}
// Check the requested plugintype is a valid one.
if (!array_key_exists($plugintype, core_component::get_plugin_types())) {
throw new restore_step_exception('incorrect_plugin_type', $plugintype);
}
// Check the requested pluginname, for the specified plugintype, is a valid one.
if (!array_key_exists($pluginname, core_component::get_plugin_list($plugintype))) {
throw new restore_step_exception('incorrect_plugin_name', array($plugintype, $pluginname));
}
// Check the requested subplugintype is a valid one.
$subplugins = core_component::get_subplugins("{$plugintype}_{$pluginname}");
if (null === $subplugins) {
throw new restore_step_exception('plugin_missing_subplugins_configuration', array($plugintype, $pluginname));
}
if (!array_key_exists($subplugintype, $subplugins)) {
throw new restore_step_exception('incorrect_subplugin_type', $subplugintype);
}
// Every subplugin optionally can have a common/parent subplugin
// class for shared stuff.
$parentclass = 'restore_' . $plugintype . '_' . $pluginname . '_' . $subplugintype . '_subplugin';
$parentfile = core_component::get_component_directory($plugintype . '_' . $pluginname) .
'/backup/moodle2/' . $parentclass . '.class.php';
if (file_exists($parentfile)) {
require_once($parentfile);
}
// Get all the restore path elements, looking across all the subplugin dirs.
$subpluginsdirs = core_component::get_plugin_list($subplugintype);
foreach ($subpluginsdirs as $name => $subpluginsdir) {
$classname = 'restore_' . $subplugintype . '_' . $name . '_subplugin';
$restorefile = $subpluginsdir . '/backup/moodle2/' . $classname . '.class.php';
if (file_exists($restorefile)) {
require_once($restorefile);
$restoresubplugin = new $classname($subplugintype, $name, $this);
// Add subplugin paths to the step.
$this->prepare_pathelements($restoresubplugin->define_subplugin_structure($element));
}
}
}
/**
* Launch all the after_execute methods present in all the processing objects
*
* This method will launch all the after_execute methods that can be defined
* both in restore_plugin and restore_structure_step classes
*
* For restore_plugin classes the name of the method to be executed will be
* "after_execute_" + connection point (as far as can be multiple connection
* points in the same class)
*
* For restore_structure_step classes is will be, simply, "after_execute". Note
* that this is executed *after* the plugin ones
*/
protected function launch_after_execute_methods() {
$alreadylaunched = array(); // To avoid multiple executions
foreach ($this->pathelements as $key => $pathelement) {
// Get the processing object
$pobject = $pathelement->get_processing_object();
// Skip null processors (child of grouped ones for sure)
if (is_null($pobject)) {
continue;
}
// Skip restore structure step processors (this)
if ($pobject instanceof restore_structure_step) {
continue;
}
// Skip already launched processing objects
if (in_array($pobject, $alreadylaunched, true)) {
continue;
}
// Add processing object to array of launched ones
$alreadylaunched[] = $pobject;
// If the processing object has support for
// launching after_execute methods, use it
if (method_exists($pobject, 'launch_after_execute_methods')) {
$pobject->launch_after_execute_methods();
}
}
// Finally execute own (restore_structure_step) after_execute method
$this->after_execute();
}
/**
* Launch all the after_restore methods present in all the processing objects
*
* This method will launch all the after_restore methods that can be defined
* both in restore_plugin class
*
* For restore_plugin classes the name of the method to be executed will be
* "after_restore_" + connection point (as far as can be multiple connection
* points in the same class)
*/
public function launch_after_restore_methods() {
$alreadylaunched = array(); // To avoid multiple executions
foreach ($this->pathelements as $pathelement) {
// Get the processing object
$pobject = $pathelement->get_processing_object();
// Skip null processors (child of grouped ones for sure)
if (is_null($pobject)) {
continue;
}
// Skip restore structure step processors (this)
if ($pobject instanceof restore_structure_step) {
continue;
}
// Skip already launched processing objects
if (in_array($pobject, $alreadylaunched, true)) {
continue;
}
// Add processing object to array of launched ones
$alreadylaunched[] = $pobject;
// If the processing object has support for
// launching after_restore methods, use it
if (method_exists($pobject, 'launch_after_restore_methods')) {
$pobject->launch_after_restore_methods();
}
}
// Finally execute own (restore_structure_step) after_restore method
$this->after_restore();
}
/**
* This method will be executed after the whole structure step have been processed
*
* After execution method for code needed to be executed after the whole structure
* has been processed. Useful for cleaning tasks, files process and others. Simply
* overwrite in in your steps if needed
*/
protected function after_execute() {
// do nothing by default
}
/**
* This method will be executed after the rest of the restore has been processed.
*
* Use if you need to update IDs based on things which are restored after this
* step has completed.
*/
protected function after_restore() {
// do nothing by default
}
/**
* Prepare the pathelements for processing, looking for duplicates, applying
* processing objects and other adjustments
*/
protected function prepare_pathelements($elementsarr) {
// First iteration, push them to new array, indexed by name
// detecting duplicates in names or paths
$names = array();
$paths = array();
foreach($elementsarr as $element) {
if (!$element instanceof restore_path_element) {
throw new restore_step_exception('restore_path_element_wrong_class', get_class($element));
}
if (array_key_exists($element->get_name(), $names)) {
throw new restore_step_exception('restore_path_element_name_alreadyexists', $element->get_name());
}
if (array_key_exists($element->get_path(), $paths)) {
throw new restore_step_exception('restore_path_element_path_alreadyexists', $element->get_path());
}
$names[$element->get_name()] = true;
$paths[$element->get_path()] = $element;
}
// Now, for each element not having one processing object, if
// not child of grouped element, assign $this (the step itself) as processing element
// Note method must exist or we'll get one @restore_path_element_exception
foreach ($paths as $pelement) {
if ($pelement->get_processing_object() === null && !$this->grouped_parent_exists($pelement, $paths)) {
$pelement->set_processing_object($this);
}
// Populate $elementsoldid and $elementsoldid based on available pathelements
$this->elementsoldid[$pelement->get_name()] = null;
$this->elementsnewid[$pelement->get_name()] = null;
}
// Done, add them to pathelements (dupes by key - path - are discarded)
$this->pathelements = array_merge($this->pathelements, $paths);
}
/**
* Given one pathelement, return true if grouped parent was found
*
* @param restore_path_element $pelement the element we are interested in.
* @param restore_path_element[] $elements the elements that exist.
* @return bool true if this element is inside a grouped parent.
*/
public function grouped_parent_exists($pelement, $elements) {
foreach ($elements as $element) {
if ($pelement->get_path() == $element->get_path()) {
continue; // Don't compare against itself.
}
// If element is grouped and parent of pelement, return true.
if ($element->is_grouped() and strpos($pelement->get_path() . '/', $element->get_path()) === 0) {
return true;
}
}
return false; // No grouped parent found.
}
/**
* To conditionally decide if one step will be executed or no
*
* For steps needing to be executed conditionally, based in dynamic
* conditions (at execution time vs at declaration time) you must
* override this function. It will return true if the step must be
* executed and false if not
*/
protected function execute_condition() {
return true;
}
/**
* Function that will return the structure to be processed by this restore_step.
* Must return one array of @restore_path_element elements
*/
abstract protected function define_structure();
}
+159
View File
@@ -0,0 +1,159 @@
<?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/>.
/**
* @package moodlecore
* @subpackage backup-plan
* @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Abstract class defining the needed stuf for one restore task (a collection of steps)
*
* TODO: Finish phpdocs
*/
abstract class restore_task extends base_task {
/**
* Constructor - instantiates one object of this class
*/
public function __construct($name, $plan = null) {
if (!is_null($plan) && !($plan instanceof restore_plan)) {
throw new restore_task_exception('wrong_restore_plan_specified');
}
parent::__construct($name, $plan);
}
public function get_restoreid() {
return $this->plan->get_restoreid();
}
public function get_info() {
return $this->plan->get_info();
}
public function get_target() {
return $this->plan->get_target();
}
public function get_userid() {
return $this->plan->get_userid();
}
public function get_decoder() {
return $this->plan->get_decoder();
}
public function is_samesite() {
return $this->plan->is_samesite();
}
public function is_missing_modules() {
return $this->plan->is_missing_modules();
}
public function is_excluding_activities() {
return $this->plan->is_excluding_activities();
}
public function set_preloaded_information() {
$this->plan->set_preloaded_information();
}
public function get_preloaded_information() {
return $this->plan->get_preloaded_information();
}
public function get_tempdir() {
return $this->plan->get_tempdir();
}
public function get_old_courseid() {
return $this->plan->get_info()->original_course_id;
}
public function get_old_contextid() {
return $this->plan->get_info()->original_course_contextid;
}
public function get_old_system_contextid() {
return $this->plan->get_info()->original_system_contextid;
}
/**
* Given a commment area, return the itemname that contains the itemid mappings
*
* By default, both are the same (commentarea = itemname), so return it. If some
* plugins use a different approach, this method can be overriden in its task.
*
* @param string $commentarea area defined for this comment
* @return string itemname that contains the related itemid mapping
*/
public function get_comment_mapping_itemname($commentarea) {
return $commentarea;
}
/**
* If the task has been executed, launch its after_restore()
* method if available
*/
public function execute_after_restore() {
if ($this->executed) {
foreach ($this->steps as $step) {
if (method_exists($step, 'launch_after_restore_methods')) {
$step->launch_after_restore_methods();
}
}
}
if ($this->executed && method_exists($this, 'after_restore')) {
$this->after_restore();
}
}
/**
* Compares the provided moodle version with the one the backup was taken from.
*
* @param int $version Moodle version number (YYYYMMDD or YYYYMMDDXX)
* @param string $operator Operator to compare the provided version to the backup version. {@see version_compare()}
* @return bool True if the comparison passes.
*/
public function backup_version_compare(int $version, string $operator) {
return $this->plan->backup_version_compare($version, $operator);
}
/**
* Compares the provided moodle release with the one the backup was taken from.
*
* @param string $release Moodle release (X.Y or X.Y.Z)
* @param string $operator Operator to compare the provided release to the backup release. {@see version_compare()}
* @return bool True if the comparison passes.
*/
public function backup_release_compare(string $release, string $operator) {
return $this->plan->backup_release_compare($release, $operator);
}
}
/*
* Exception class used by all the @restore_task stuff
*/
class restore_task_exception extends base_task_exception {
public function __construct($errorcode, $a=NULL, $debuginfo=null) {
parent::__construct($errorcode, $a, $debuginfo);
}
}
+202
View File
@@ -0,0 +1,202 @@
<?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/>.
/**
* @package core_backup
* @category phpunit
* @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
// Include all the needed stuff
global $CFG;
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
require_once($CFG->dirroot . '/backup/moodle2/backup_custom_fields.php');
require_once($CFG->dirroot . '/backup/moodle2/backup_subplugin.class.php');
/**
* Instantiable class extending base_plan in order to be able to perform tests
*/
class mock_base_plan extends base_plan {
public function build() {
}
public function get_progress() {
return null;
}
}
/**
* Instantiable class extending base_step in order to be able to perform tests
*/
class mock_base_step extends base_step {
public function execute() {
}
}
/**
* Instantiable class extending backup_step in order to be able to perform tests
*/
class mock_backup_step extends backup_step {
public function execute() {
}
}
/**
* Instantiable class extending backup_task in order to mockup get_taskbasepath()
*/
class mock_backup_task_basepath extends backup_task {
/** @var string name of the mod plugin (activity) being used in the tests */
private $modulename;
public function build() {
// Nothing to do
}
public function define_settings() {
// Nothing to do
}
public function get_taskbasepath() {
global $CFG;
return $CFG->tempdir . '/test';
}
public function set_modulename($modulename) {
$this->modulename = $modulename;
}
public function get_modulename() {
return $this->modulename;
}
}
/**
* Instantiable class extending restore_task in order to mockup get_taskbasepath()
*/
class mock_restore_task_basepath extends restore_task {
/** @var string name of the mod plugin (activity) being used in the tests */
private $modulename;
public function build() {
// Nothing to do.
}
public function define_settings() {
// Nothing to do.
}
public function set_modulename($modulename) {
$this->modulename = $modulename;
}
public function get_modulename() {
return $this->modulename;
}
}
/**
* Instantiable class extending backup_structure_step in order to be able to perform tests
*/
class mock_backup_structure_step extends backup_structure_step {
public function define_structure() {
// Create really simple structure (1 nested with 1 attr and 2 fields)
$test = new backup_nested_element('test',
array('id'),
array('field1', 'field2')
);
$test->set_source_array(array(array('id' => 1, 'field1' => 'value1', 'field2' => 'value2')));
return $test;
}
public function add_plugin_structure($plugintype, $element, $multiple) {
parent::add_plugin_structure($plugintype, $element, $multiple);
}
public function add_subplugin_structure($subplugintype, $element, $multiple, $plugintype = null, $pluginname = null) {
parent::add_subplugin_structure($subplugintype, $element, $multiple, $plugintype, $pluginname);
}
}
class mock_restore_structure_step extends restore_structure_step {
public function define_structure() {
// Create a really simple structure (1 element).
$test = new restore_path_element('test', '/tests/test');
return array($test);
}
public function add_plugin_structure($plugintype, $element) {
parent::add_plugin_structure($plugintype, $element);
}
public function add_subplugin_structure($subplugintype, $element, $plugintype = null, $pluginname = null) {
parent::add_subplugin_structure($subplugintype, $element, $plugintype, $pluginname);
}
public function get_pathelements() {
return $this->pathelements;
}
}
/**
* Instantiable class extending activity_backup_setting to be added to task and perform tests
*/
class mock_fullpath_activity_setting extends activity_backup_setting {
public function process_change($setting, $ctype, $oldv) {
// Nothing to do
}
}
/**
* Instantiable class extending activity_backup_setting to be added to task and perform tests
*/
class mock_backupid_activity_setting extends activity_backup_setting {
public function process_change($setting, $ctype, $oldv) {
// Nothing to do
}
}
/**
* Instantiable class extending base_task in order to be able to perform tests
*/
class mock_base_task extends base_task {
public function build() {
}
public function define_settings() {
}
}
/**
* Instantiable class extending backup_task in order to be able to perform tests
*/
class mock_backup_task extends backup_task {
public function build() {
}
public function define_settings() {
}
}
+155
View File
@@ -0,0 +1,155 @@
<?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 backup;
use backup_controller;
use backup_controller_exception;
use backup_plan;
use backup_plan_exception;
use base_plan;
use base_plan_exception;
defined('MOODLE_INTERNAL') || die();
require_once(__DIR__.'/fixtures/plan_fixtures.php');
/**
* @package core_backup
* @category test
* @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class plan_test extends \advanced_testcase {
protected $moduleid; // course_modules id used for testing
protected $sectionid; // course_sections id used for testing
protected $courseid; // course id used for testing
protected $userid; // user record used for testing
protected function setUp(): void {
global $DB, $CFG;
parent::setUp();
$this->resetAfterTest(true);
$course = $this->getDataGenerator()->create_course();
$page = $this->getDataGenerator()->create_module('page', array('course'=>$course->id), array('section'=>3));
$coursemodule = $DB->get_record('course_modules', array('id'=>$page->cmid));
$this->moduleid = $coursemodule->id;
$this->sectionid = $DB->get_field("course_sections", 'id', array("section"=>$coursemodule->section, "course"=>$course->id));
$this->courseid = $coursemodule->course;
$this->userid = 2; // admin
// Disable all loggers
$CFG->backup_error_log_logger_level = backup::LOG_NONE;
$CFG->backup_file_logger_level = backup::LOG_NONE;
$CFG->backup_database_logger_level = backup::LOG_NONE;
$CFG->backup_file_logger_level_extra = backup::LOG_NONE;
}
/**
* test base_plan class
*/
function test_base_plan(): void {
// Instantiate
$bp = new \mock_base_plan('name');
$this->assertTrue($bp instanceof base_plan);
$this->assertEquals($bp->get_name(), 'name');
$this->assertTrue(is_array($bp->get_settings()));
$this->assertEquals(count($bp->get_settings()), 0);
$this->assertTrue(is_array($bp->get_tasks()));
$this->assertEquals(count($bp->get_tasks()), 0);
}
/**
* test backup_plan class
*/
function test_backup_plan(): void {
// We need one (non interactive) controller for instantiating plan
$bc = new backup_controller(backup::TYPE_1ACTIVITY, $this->moduleid, backup::FORMAT_MOODLE,
backup::INTERACTIVE_NO, backup::MODE_GENERAL, $this->userid);
// Instantiate one backup plan
$bp = new backup_plan($bc);
$this->assertTrue($bp instanceof backup_plan);
$this->assertEquals($bp->get_name(), 'backup_plan');
// Calculate checksum and check it
$checksum = $bp->calculate_checksum();
$this->assertTrue($bp->is_checksum_correct($checksum));
$bc->destroy();
}
/**
* wrong base_plan class tests
*/
function test_base_plan_wrong(): void {
// We need one (non interactive) controller for instantiating plan
$bc = new backup_controller(backup::TYPE_1ACTIVITY, $this->moduleid, backup::FORMAT_MOODLE,
backup::INTERACTIVE_NO, backup::MODE_GENERAL, $this->userid);
// Instantiate one backup plan
$bp = new backup_plan($bc);
// Add wrong task
try {
$bp->add_task(new \stdClass());
$this->assertTrue(false, 'base_plan_exception expected');
} catch (\Exception $e) {
$this->assertTrue($e instanceof base_plan_exception);
$this->assertEquals($e->errorcode, 'wrong_base_task_specified');
}
}
/**
* wrong backup_plan class tests
*/
function test_backup_plan_wrong(): void {
// Try to pass one wrong controller
try {
$bp = new backup_plan(new \stdClass());
$this->assertTrue(false, 'backup_plan_exception expected');
} catch (\Exception $e) {
$this->assertTrue($e instanceof backup_plan_exception);
$this->assertEquals($e->errorcode, 'wrong_backup_controller_specified');
}
try {
$bp = new backup_plan(null);
$this->assertTrue(false, 'backup_plan_exception expected');
} catch (\Exception $e) {
$this->assertTrue($e instanceof backup_plan_exception);
$this->assertEquals($e->errorcode, 'wrong_backup_controller_specified');
}
// Try to build one non-existent format plan (when creating the controller)
// We need one (non interactive) controller for instatiating plan
try {
$bc = new backup_controller(backup::TYPE_1ACTIVITY, $this->moduleid, 'non_existing_format',
backup::INTERACTIVE_NO, backup::MODE_GENERAL, $this->userid);
$this->assertTrue(false, 'backup_controller_exception expected');
} catch (\Exception $e) {
$this->assertTrue($e instanceof backup_controller_exception);
$this->assertEquals($e->errorcode, 'backup_check_unsupported_format');
$this->assertEquals($e->a, 'non_existing_format');
}
}
}
+483
View File
@@ -0,0 +1,483 @@
<?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 backup;
use backup_controller;
use backup_nested_element;
use backup_optigroup;
use backup_plan;
use backup_plugin_element;
use backup_step;
use backup_step_exception;
use backup_subplugin_element;
use base_step;
use base_step_exception;
use restore_path_element;
use restore_plugin;
use restore_step_exception;
use restore_subplugin;
defined('MOODLE_INTERNAL') || die();
require_once(__DIR__.'/fixtures/plan_fixtures.php');
/**
* @package core_backup
* @category test
* @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class step_test extends \advanced_testcase {
protected $moduleid; // course_modules id used for testing
protected $sectionid; // course_sections id used for testing
protected $courseid; // course id used for testing
protected $userid; // user record used for testing
protected function setUp(): void {
global $DB, $CFG;
parent::setUp();
$this->resetAfterTest(true);
$course = $this->getDataGenerator()->create_course();
$page = $this->getDataGenerator()->create_module('page', array('course'=>$course->id), array('section'=>3));
$coursemodule = $DB->get_record('course_modules', array('id'=>$page->cmid));
$this->moduleid = $coursemodule->id;
$this->sectionid = $DB->get_field("course_sections", 'id', array("section"=>$coursemodule->section, "course"=>$course->id));
$this->courseid = $coursemodule->course;
$this->userid = 2; // admin
// Disable all loggers
$CFG->backup_error_log_logger_level = backup::LOG_NONE;
$CFG->backup_file_logger_level = backup::LOG_NONE;
$CFG->backup_database_logger_level = backup::LOG_NONE;
$CFG->backup_file_logger_level_extra = backup::LOG_NONE;
}
/**
* test base_step class
*/
function test_base_step(): void {
$bp = new \mock_base_plan('planname'); // We need one plan
$bt = new \mock_base_task('taskname', $bp); // We need one task
// Instantiate
$bs = new \mock_base_step('stepname', $bt);
$this->assertTrue($bs instanceof base_step);
$this->assertEquals($bs->get_name(), 'stepname');
}
/**
* test backup_step class
*/
function test_backup_step(): void {
// We need one (non interactive) controller for instatiating plan
$bc = new backup_controller(backup::TYPE_1ACTIVITY, $this->moduleid, backup::FORMAT_MOODLE,
backup::INTERACTIVE_NO, backup::MODE_GENERAL, $this->userid);
// We need one plan
$bp = new backup_plan($bc);
// We need one task
$bt = new \mock_backup_task('taskname', $bp);
// Instantiate step
$bs = new \mock_backup_step('stepname', $bt);
$this->assertTrue($bs instanceof backup_step);
$this->assertEquals($bs->get_name(), 'stepname');
$bc->destroy();
}
/**
* test restore_step class, decrypt method
*/
public function test_restore_step_decrypt(): void {
$this->resetAfterTest(true);
if (!function_exists('openssl_encrypt')) {
$this->markTestSkipped('OpenSSL extension is not loaded.');
} else if (!function_exists('hash_hmac')) {
$this->markTestSkipped('Hash extension is not loaded.');
} else if (!in_array(backup::CIPHER, openssl_get_cipher_methods())) {
$this->markTestSkipped('Expected cipher not available: ' . backup::CIPHER);
}
$bt = new \mock_restore_task_basepath('taskname');
$bs = new \mock_restore_structure_step('steptest', null, $bt);
$this->assertTrue(method_exists($bs, 'decrypt'));
// Let's prepare a string for being decrypted.
$secret = 'This is a secret message that nobody else will be able to read but me 💩 ';
$key = hash('md5', 'Moodle rocks and this is not secure key, who cares, it is a test');
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length(backup::CIPHER));
$message = $iv . openssl_encrypt($secret, backup::CIPHER, $key, OPENSSL_RAW_DATA, $iv);
$hmac = hash_hmac('sha256', $message, $key, true);
$crypt = base64_encode($hmac . $message);
// Running it without a key configured, returns null.
$this->assertNull($bs->decrypt($crypt));
// Store the key into config.
set_config('backup_encryptkey', base64_encode($key), 'backup');
// Verify decrypt works and returns original.
$this->assertSame($secret, $bs->decrypt($crypt));
// Finally, test the integrity failure detection is working.
// (this can be caused by changed hmac, key or message, in
// this case we are just forcing it via changed hmac).
$hmac = md5($message);
$crypt = base64_encode($hmac . $message);
$this->assertNull($bs->decrypt($crypt));
}
/**
* test backup_structure_step class
*/
function test_backup_structure_step(): void {
global $CFG;
$file = $CFG->tempdir . '/test/test_backup_structure_step.txt';
// Remove the test dir and any content
@remove_dir(dirname($file));
// Recreate test dir
if (!check_dir_exists(dirname($file), true, true)) {
throw new \moodle_exception('error_creating_temp_dir', 'error', dirname($file));
}
// We need one (non interactive) controller for instatiating plan
$bc = new backup_controller(backup::TYPE_1ACTIVITY, $this->moduleid, backup::FORMAT_MOODLE,
backup::INTERACTIVE_NO, backup::MODE_GENERAL, $this->userid);
// We need one plan
$bp = new backup_plan($bc);
// We need one task with mocked basepath
$bt = new \mock_backup_task_basepath('taskname');
$bp->add_task($bt);
// Instantiate backup_structure_step (and add it to task)
$bs = new \mock_backup_structure_step('steptest', basename($file), $bt);
// Execute backup_structure_step
$bs->execute();
// Test file has been created
$this->assertTrue(file_exists($file));
// Some simple tests with contents
$contents = file_get_contents($file);
$this->assertTrue(strpos($contents, '<?xml version="1.0"') !== false);
$this->assertTrue(strpos($contents, '<test id="1">') !== false);
$this->assertTrue(strpos($contents, '<field1>value1</field1>') !== false);
$this->assertTrue(strpos($contents, '<field2>value2</field2>') !== false);
$this->assertTrue(strpos($contents, '</test>') !== false);
$bc->destroy();
unlink($file); // delete file
// Remove the test dir and any content
@remove_dir(dirname($file));
}
/**
* Verify the add_plugin_structure() backup method behavior and created structures.
*/
public function test_backup_structure_step_add_plugin_structure(): void {
// Create mocked task, step and element.
$bt = new \mock_backup_task_basepath('taskname');
$bs = new \mock_backup_structure_step('steptest', null, $bt);
$el = new backup_nested_element('question', array('id'), array('one', 'two', 'qtype'));
// Wrong plugintype.
try {
$bs->add_plugin_structure('fakeplugin', $el, true);
$this->assertTrue(false, 'base_step_exception expected');
} catch (\Exception $e) {
$this->assertTrue($e instanceof backup_step_exception);
$this->assertEquals('incorrect_plugin_type', $e->errorcode);
}
// Correct plugintype qtype call (@ 'question' level).
$bs->add_plugin_structure('qtype', $el, false);
$ch = $el->get_children();
$this->assertEquals(1, count($ch));
$og = reset($ch);
$this->assertTrue($og instanceof backup_optigroup);
$ch = $og->get_children();
$this->assertTrue(array_key_exists('optigroup_qtype_calculatedsimple_question', $ch));
$this->assertTrue($ch['optigroup_qtype_calculatedsimple_question'] instanceof backup_plugin_element);
}
/**
* Verify the add_subplugin_structure() backup method behavior and created structures.
*/
public function test_backup_structure_step_add_subplugin_structure(): void {
// Create mocked task, step and element.
$bt = new \mock_backup_task_basepath('taskname');
$bs = new \mock_backup_structure_step('steptest', null, $bt);
$el = new backup_nested_element('workshop', array('id'), array('one', 'two', 'qtype'));
// Wrong plugin type.
try {
$bs->add_subplugin_structure('fakesubplugin', $el, true, 'fakeplugintype', 'fakepluginname');
$this->assertTrue(false, 'base_step_exception expected');
} catch (\Exception $e) {
$this->assertTrue($e instanceof backup_step_exception);
$this->assertEquals('incorrect_plugin_type', $e->errorcode);
}
// Wrong plugin type.
try {
$bs->add_subplugin_structure('fakesubplugin', $el, true, 'mod', 'fakepluginname');
$this->assertTrue(false, 'base_step_exception expected');
} catch (\Exception $e) {
$this->assertTrue($e instanceof backup_step_exception);
$this->assertEquals('incorrect_plugin_name', $e->errorcode);
}
// Wrong plugin not having subplugins.
try {
$bs->add_subplugin_structure('fakesubplugin', $el, true, 'mod', 'page');
$this->assertTrue(false, 'base_step_exception expected');
} catch (\Exception $e) {
$this->assertTrue($e instanceof backup_step_exception);
$this->assertEquals('plugin_missing_subplugins_configuration', $e->errorcode);
}
// Wrong BC (defaulting to mod and modulename) use not having subplugins.
try {
$bt->set_modulename('page');
$bs->add_subplugin_structure('fakesubplugin', $el, true);
$this->assertTrue(false, 'base_step_exception expected');
} catch (\Exception $e) {
$this->assertTrue($e instanceof backup_step_exception);
$this->assertEquals('plugin_missing_subplugins_configuration', $e->errorcode);
}
// Wrong subplugin type.
try {
$bs->add_subplugin_structure('fakesubplugin', $el, true, 'mod', 'workshop');
$this->assertTrue(false, 'base_step_exception expected');
} catch (\Exception $e) {
$this->assertTrue($e instanceof backup_step_exception);
$this->assertEquals('incorrect_subplugin_type', $e->errorcode);
}
// Wrong BC subplugin type.
try {
$bt->set_modulename('workshop');
$bs->add_subplugin_structure('fakesubplugin', $el, true);
$this->assertTrue(false, 'base_step_exception expected');
} catch (\Exception $e) {
$this->assertTrue($e instanceof backup_step_exception);
$this->assertEquals('incorrect_subplugin_type', $e->errorcode);
}
// Correct call to workshopform subplugin (@ 'workshop' level).
$bs->add_subplugin_structure('workshopform', $el, true, 'mod', 'workshop');
$ch = $el->get_children();
$this->assertEquals(1, count($ch));
$og = reset($ch);
$this->assertTrue($og instanceof backup_optigroup);
$ch = $og->get_children();
$this->assertTrue(array_key_exists('optigroup_workshopform_accumulative_workshop', $ch));
$this->assertTrue($ch['optigroup_workshopform_accumulative_workshop'] instanceof backup_subplugin_element);
// Correct BC call to workshopform subplugin (@ 'assessment' level).
$el = new backup_nested_element('assessment', array('id'), array('one', 'two', 'qtype'));
$bt->set_modulename('workshop');
$bs->add_subplugin_structure('workshopform', $el, true);
$ch = $el->get_children();
$this->assertEquals(1, count($ch));
$og = reset($ch);
$this->assertTrue($og instanceof backup_optigroup);
$ch = $og->get_children();
$this->assertTrue(array_key_exists('optigroup_workshopform_accumulative_assessment', $ch));
$this->assertTrue($ch['optigroup_workshopform_accumulative_assessment'] instanceof backup_subplugin_element);
// TODO: Add some test covering a non-mod subplugin once we have some implemented in core.
}
/**
* Verify the add_plugin_structure() restore method behavior and created structures.
*/
public function test_restore_structure_step_add_plugin_structure(): void {
// Create mocked task, step and element.
$bt = new \mock_restore_task_basepath('taskname');
$bs = new \mock_restore_structure_step('steptest', null, $bt);
$el = new restore_path_element('question', '/some/path/to/question');
// Wrong plugintype.
try {
$bs->add_plugin_structure('fakeplugin', $el);
$this->assertTrue(false, 'base_step_exception expected');
} catch (\Exception $e) {
$this->assertTrue($e instanceof restore_step_exception);
$this->assertEquals('incorrect_plugin_type', $e->errorcode);
}
// Correct plugintype qtype call (@ 'question' level).
$bs->add_plugin_structure('qtype', $el);
$patheles = $bs->get_pathelements();
// Verify some well-known qtype plugin restore_path_elements have been added.
$keys = array(
'/some/path/to/question/plugin_qtype_calculated_question/answers/answer',
'/some/path/to/question/plugin_qtype_calculated_question/dataset_definitions/dataset_definition',
'/some/path/to/question/plugin_qtype_calculated_question/calculated_options/calculated_option',
'/some/path/to/question/plugin_qtype_essay_question/essay',
'/some/path/to/question/plugin_qtype_random_question',
'/some/path/to/question/plugin_qtype_truefalse_question/answers/answer');
foreach ($keys as $key) {
// Verify the element exists.
$this->assertArrayHasKey($key, $patheles);
// Verify the element is a restore_path_element.
$this->assertTrue($patheles[$key] instanceof restore_path_element);
// Check it has a processing object.
$po = $patheles[$key]->get_processing_object();
$this->assertTrue($po instanceof restore_plugin);
}
}
/**
* Verify the add_subplugin_structure() restore method behavior and created structures.
*/
public function test_restore_structure_step_add_subplugin_structure(): void {
// Create mocked task, step and element.
$bt = new \mock_restore_task_basepath('taskname');
$bs = new \mock_restore_structure_step('steptest', null, $bt);
$el = new restore_path_element('workshop', '/path/to/workshop');
// Wrong plugin type.
try {
$bs->add_subplugin_structure('fakesubplugin', $el, 'fakeplugintype', 'fakepluginname');
$this->assertTrue(false, 'base_step_exception expected');
} catch (\Exception $e) {
$this->assertTrue($e instanceof restore_step_exception);
$this->assertEquals('incorrect_plugin_type', $e->errorcode);
}
// Wrong plugin type.
try {
$bs->add_subplugin_structure('fakesubplugin', $el, 'mod', 'fakepluginname');
$this->assertTrue(false, 'base_step_exception expected');
} catch (\Exception $e) {
$this->assertTrue($e instanceof restore_step_exception);
$this->assertEquals('incorrect_plugin_name', $e->errorcode);
}
// Wrong plugin not having subplugins.
try {
$bs->add_subplugin_structure('fakesubplugin', $el, 'mod', 'page');
$this->assertTrue(false, 'base_step_exception expected');
} catch (\Exception $e) {
$this->assertTrue($e instanceof restore_step_exception);
$this->assertEquals('plugin_missing_subplugins_configuration', $e->errorcode);
}
// Wrong BC (defaulting to mod and modulename) use not having subplugins.
try {
$bt->set_modulename('page');
$bs->add_subplugin_structure('fakesubplugin', $el);
$this->assertTrue(false, 'base_step_exception expected');
} catch (\Exception $e) {
$this->assertTrue($e instanceof restore_step_exception);
$this->assertEquals('plugin_missing_subplugins_configuration', $e->errorcode);
}
// Wrong subplugin type.
try {
$bs->add_subplugin_structure('fakesubplugin', $el, 'mod', 'workshop');
$this->assertTrue(false, 'base_step_exception expected');
} catch (\Exception $e) {
$this->assertTrue($e instanceof restore_step_exception);
$this->assertEquals('incorrect_subplugin_type', $e->errorcode);
}
// Wrong BC subplugin type.
try {
$bt->set_modulename('workshop');
$bs->add_subplugin_structure('fakesubplugin', $el);
$this->assertTrue(false, 'base_step_exception expected');
} catch (\Exception $e) {
$this->assertTrue($e instanceof restore_step_exception);
$this->assertEquals('incorrect_subplugin_type', $e->errorcode);
}
// Correct call to workshopform subplugin (@ 'workshop' level).
$bt = new \mock_restore_task_basepath('taskname');
$bs = new \mock_restore_structure_step('steptest', null, $bt);
$el = new restore_path_element('workshop', '/path/to/workshop');
$bs->add_subplugin_structure('workshopform', $el, 'mod', 'workshop');
$patheles = $bs->get_pathelements();
// Verify some well-known workshopform subplugin restore_path_elements have been added.
$keys = array(
'/path/to/workshop/subplugin_workshopform_accumulative_workshop/workshopform_accumulative_dimension',
'/path/to/workshop/subplugin_workshopform_comments_workshop/workshopform_comments_dimension',
'/path/to/workshop/subplugin_workshopform_numerrors_workshop/workshopform_numerrors_map',
'/path/to/workshop/subplugin_workshopform_rubric_workshop/workshopform_rubric_config');
foreach ($keys as $key) {
// Verify the element exists.
$this->assertArrayHasKey($key, $patheles);
// Verify the element is a restore_path_element.
$this->assertTrue($patheles[$key] instanceof restore_path_element);
// Check it has a processing object.
$po = $patheles[$key]->get_processing_object();
$this->assertTrue($po instanceof restore_subplugin);
}
// Correct BC call to workshopform subplugin (@ 'assessment' level).
$bt = new \mock_restore_task_basepath('taskname');
$bs = new \mock_restore_structure_step('steptest', null, $bt);
$el = new restore_path_element('assessment', '/a/assessment');
$bt->set_modulename('workshop');
$bs->add_subplugin_structure('workshopform', $el);
$patheles = $bs->get_pathelements();
// Verify some well-known workshopform subplugin restore_path_elements have been added.
$keys = array(
'/a/assessment/subplugin_workshopform_accumulative_assessment/workshopform_accumulative_grade',
'/a/assessment/subplugin_workshopform_comments_assessment/workshopform_comments_grade',
'/a/assessment/subplugin_workshopform_numerrors_assessment/workshopform_numerrors_grade',
'/a/assessment/subplugin_workshopform_rubric_assessment/workshopform_rubric_grade');
foreach ($keys as $key) {
// Verify the element exists.
$this->assertArrayHasKey($key, $patheles);
// Verify the element is a restore_path_element.
$this->assertTrue($patheles[$key] instanceof restore_path_element);
// Check it has a processing object.
$po = $patheles[$key]->get_processing_object();
$this->assertTrue($po instanceof restore_subplugin);
}
// TODO: Add some test covering a non-mod subplugin once we have some implemented in core.
}
/**
* wrong base_step class tests
*/
function test_base_step_wrong(): void {
// Try to pass one wrong task
try {
$bt = new \mock_base_step('teststep', new \stdClass());
$this->assertTrue(false, 'base_step_exception expected');
} catch (\Exception $e) {
$this->assertTrue($e instanceof base_step_exception);
$this->assertEquals($e->errorcode, 'wrong_base_task_specified');
}
}
/**
* wrong backup_step class tests
*/
function test_backup_test_wrong(): void {
// Try to pass one wrong task
try {
$bt = new \mock_backup_step('teststep', new \stdClass());
$this->assertTrue(false, 'backup_step_exception expected');
} catch (\Exception $e) {
$this->assertTrue($e instanceof backup_step_exception);
$this->assertEquals($e->errorcode, 'wrong_backup_task_specified');
}
}
}
+146
View File
@@ -0,0 +1,146 @@
<?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 backup;
use base_task;
use base_task_exception;
use backup_controller;
use backup_plan;
use backup_task;
use backup_task_exception;
defined('MOODLE_INTERNAL') || die();
require_once(__DIR__.'/fixtures/plan_fixtures.php');
/**
* @package core_backup
* @category test
* @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class task_test extends \advanced_testcase {
protected $moduleid; // course_modules id used for testing
protected $sectionid; // course_sections id used for testing
protected $courseid; // course id used for testing
protected $userid; // user record used for testing
protected function setUp(): void {
global $DB, $CFG;
parent::setUp();
$this->resetAfterTest(true);
$course = $this->getDataGenerator()->create_course();
$page = $this->getDataGenerator()->create_module('page', array('course'=>$course->id), array('section'=>3));
$coursemodule = $DB->get_record('course_modules', array('id'=>$page->cmid));
$this->moduleid = $coursemodule->id;
$this->sectionid = $DB->get_field("course_sections", 'id', array("section"=>$coursemodule->section, "course"=>$course->id));
$this->courseid = $coursemodule->course;
$this->userid = 2; // admin
// Disable all loggers
$CFG->backup_error_log_logger_level = backup::LOG_NONE;
$CFG->backup_file_logger_level = backup::LOG_NONE;
$CFG->backup_database_logger_level = backup::LOG_NONE;
$CFG->backup_file_logger_level_extra = backup::LOG_NONE;
}
/**
* test base_task class
*/
function test_base_task(): void {
$bp = new \mock_base_plan('planname'); // We need one plan
// Instantiate
$bt = new \mock_base_task('taskname', $bp);
$this->assertTrue($bt instanceof base_task);
$this->assertEquals($bt->get_name(), 'taskname');
$this->assertTrue(is_array($bt->get_settings()));
$this->assertEquals(count($bt->get_settings()), 0);
$this->assertTrue(is_array($bt->get_steps()));
$this->assertEquals(count($bt->get_steps()), 0);
}
/**
* test backup_task class
*/
function test_backup_task(): void {
// We need one (non interactive) controller for instatiating plan
$bc = new backup_controller(backup::TYPE_1ACTIVITY, $this->moduleid, backup::FORMAT_MOODLE,
backup::INTERACTIVE_NO, backup::MODE_GENERAL, $this->userid);
// We need one plan
$bp = new backup_plan($bc);
// Instantiate task
$bt = new \mock_backup_task('taskname', $bp);
$this->assertTrue($bt instanceof backup_task);
$this->assertEquals($bt->get_name(), 'taskname');
// Calculate checksum and check it
$checksum = $bt->calculate_checksum();
$this->assertTrue($bt->is_checksum_correct($checksum));
$bc->destroy();
}
/**
* wrong base_task class tests
*/
function test_base_task_wrong(): void {
// Try to pass one wrong plan
try {
$bt = new \mock_base_task('tasktest', new \stdClass());
$this->assertTrue(false, 'base_task_exception expected');
} catch (\Exception $e) {
$this->assertTrue($e instanceof base_task_exception);
$this->assertEquals($e->errorcode, 'wrong_base_plan_specified');
}
// Add wrong step to task
$bp = new \mock_base_plan('planname'); // We need one plan
// Instantiate
$bt = new \mock_base_task('taskname', $bp);
try {
$bt->add_step(new \stdClass());
$this->assertTrue(false, 'base_task_exception expected');
} catch (\Exception $e) {
$this->assertTrue($e instanceof base_task_exception);
$this->assertEquals($e->errorcode, 'wrong_base_step_specified');
}
}
/**
* wrong backup_task class tests
*/
function test_backup_task_wrong(): void {
// Try to pass one wrong plan
try {
$bt = new \mock_backup_task('tasktest', new \stdClass());
$this->assertTrue(false, 'backup_task_exception expected');
} catch (\Exception $e) {
$this->assertTrue($e instanceof backup_task_exception);
$this->assertEquals($e->errorcode, 'wrong_backup_plan_specified');
}
}
}