���ѧۧݧ�ӧ�� �ާ֧ߧ֧էا֧� - ���֧էѧܧ�ڧ��ӧѧ�� - /home3/cpr76684/public_html/plan.tar
���ѧ٧ѧ�
backup_task.class.php 0000644 00000004075 15152320571 0010657 0 ustar 00 <?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); } } restore_step.class.php 0000644 00000013717 15152320571 0011111 0 ustar 00 <?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); } } backup_execution_step.class.php 0000644 00000002466 15152320571 0012755 0 ustar 00 <?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(); } base_plan.class.php 0000644 00000016424 15152320571 0010315 0 ustar 00 <?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) */ public abstract 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 */ public abstract 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); } } tests/fixtures/plan_fixtures.php 0000644 00000012671 15152320571 0013163 0 ustar 00 <?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'); /** * 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() { } } tests/task_test.php 0000644 00000012102 15152320571 0010415 0 ustar 00 <?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() { $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() { // 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() { // 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() { // 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'); } } } tests/step_test.php 0000644 00000053047 15152320571 0010443 0 ustar 00 <?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() { $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() { // 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() { $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() { 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() { // 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() { // 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() { // 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() { // 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() { // 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() { // 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'); } } } tests/plan_test.php 0000644 00000013352 15152320571 0010415 0 ustar 00 <?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() { // 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() { // 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() { // 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() { // 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'); } } } backup_structure_step.class.php 0000644 00000026457 15152320571 0013020 0 ustar 00 <?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 { protected $filename; // Name of the file to be generated protected $contenttransformer; // xml content transformer being used // (need it here, apart from xml_writer, // thanks to serialized data to process - // say thanks to blocks!) /** * 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(); } base_task.class.php 0000644 00000021216 15152320571 0010320 0 ustar 00 <?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 { protected $name; // One simple name for identification purposes protected $plan; // Plan this is part of protected $settings; // One array of base_setting elements to define this task protected $steps; // One array of base_step elements protected $built; // Flag to know if one task has been built 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) */ public abstract 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. */ protected abstract 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); } } backup_plan.class.php 0000644 00000013013 15152320571 0010637 0 ustar 00 <?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); } } restore_execution_step.class.php 0000644 00000002471 15152320571 0013167 0 ustar 00 <?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(); } restore_structure_step.class.php 0000644 00000060546 15152320571 0013233 0 ustar 00 <?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(); } base_step.class.php 0000644 00000007622 15152320571 0010336 0 ustar 00 <?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 { protected $name; // One simple name for identification purposes protected $task; // Task this is part of /** * 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); } } backup_step.class.php 0000644 00000003467 15152320571 0010674 0 ustar 00 <?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); } } restore_task.class.php 0000644 00000007775 15152320571 0011107 0 ustar 00 <?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(); } } } /* * 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); } } restore_plan.class.php 0000644 00000016474 15152320571 0011073 0 ustar 00 <?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(); } } /* * 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); } }
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | ���֧ߧ֧�ѧ�ڧ� ����ѧߧڧ��: 0 |
proxy
|
phpinfo
|
���ѧ����ۧܧ�