first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
@@ -0,0 +1,121 @@
<?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_element_struct_exception;
use encrypted_final_element;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
require_once($CFG->dirroot . '/backup/moodle2/backup_custom_fields.php');
/**
* Tests for the handling of encrypted contents in backup and restore.
*
* @package core_backup
* @copyright 2016 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class backup_encrypted_content_test extends \advanced_testcase {
public function setUp(): void {
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);
}
}
public function test_encrypted_final_element(): void {
$this->resetAfterTest(true);
// Some basic verifications.
$efe = new encrypted_final_element('test', array('encrypted'));
$this->assertInstanceOf('encrypted_final_element', $efe);
$this->assertSame('test', $efe->get_name());
$atts = $efe->get_attributes();
$this->assertCount(1, $atts);
$att = reset($atts);
$this->assertInstanceOf('backup_attribute', $att);
$this->assertSame('encrypted', $att->get_name());
// Using a manually defined (incorrect length) key.
$efe = new encrypted_final_element('test', array('encrypted'));
$key = 'this_in_not_correct_32_byte_key';
try {
set_config('backup_encryptkey', base64_encode($key), 'backup');
$efe->set_value('tiny_secret');
$this->fail('Expecting base_element_struct_exception exception, none happened');
} catch (\Exception $e) {
$this->assertInstanceOf('base_element_struct_exception', $e);
$this->assertEquals('encrypted_final_element incorrect key length', $e->errorcode);
}
// Using a manually defined (correct length) key.
$efe = new encrypted_final_element('test', array('testattr', 'encrypted'));
$key = hash('md5', 'Moodle rocks and this is not secure key, who cares, it is a test');
set_config('backup_encryptkey', base64_encode($key), 'backup');
$this->assertEmpty($efe->get_value());
$secret = 'This is a secret message that nobody else will be able to read but me 💩 ';
$efe->set_value($secret);
$atts = $efe->get_attributes();
$this->assertCount(2, $atts);
$this->assertArrayHasKey('encrypted', $atts); // We added it explicitly.
$this->assertTrue($atts['encrypted']->is_set());
$this->assertSame('true', $atts['encrypted']->get_value());
$this->assertNotEmpty($efe->get_value());
$this->assertTrue($efe->is_set());
// Get the crypted content and decrypt it manually.
$ctext = $efe->get_value();
$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($ctext)));
$this->assertSame(hash_hmac('sha256', $iv . $text, $key, true), $hmac);
$this->assertSame($secret, openssl_decrypt($text, backup::CIPHER, $key, OPENSSL_RAW_DATA, $iv));
// Using the default site-generated key.
$efe = new encrypted_final_element('test', array('testattr'));
$this->assertEmpty($efe->get_value());
$secret = 'This is a secret message that nobody else will be able to read but me 💩 ';
$efe->set_value($secret);
$atts = $efe->get_attributes();
$this->assertCount(2, $atts);
$this->assertArrayHasKey('encrypted', $atts); // Was added automatcally, we did not specify it.
$this->assertTrue($atts['encrypted']->is_set());
$this->assertSame('true', $atts['encrypted']->get_value());
$this->assertNotEmpty($efe->get_value());
$this->assertTrue($efe->is_set());
// Get the crypted content and decrypt it manually.
$ctext = $efe->get_value();
$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($ctext)));
$key = base64_decode(get_config('backup', 'backup_encryptkey'));
$this->assertSame(hash_hmac('sha256', $iv . $text, $key, true), $hmac);
$this->assertSame($secret, openssl_decrypt($text, backup::CIPHER, $key, OPENSSL_RAW_DATA, $iv));
}
}
@@ -0,0 +1,92 @@
<?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_section_structure_step;
use backup_section_task;
/**
* Tests for Moodle 2 steplib classes.
*
* @package core_backup
* @copyright 2023 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class backup_stepslib_test extends \advanced_testcase {
/**
* Setup to include all libraries.
*/
public static function setUpBeforeClass(): void {
global $CFG;
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
require_once($CFG->dirroot . '/backup/moodle2/backup_stepslib.php');
}
/**
* Test for the section structure step included elements.
*
* @covers \backup_section_structure_step::define_structure
*/
public function test_backup_section_structure_step(): void {
global $USER;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course(['numsections' => 3, 'format' => 'topics']);
$this->setAdminUser();
$step = new backup_section_structure_step('section_commons', 'section.xml');
// The backup_section_structure_step requires a complex dependency sequence
// but it does not have an easy dependency injection system.
// We create a real backup plan to get the task dependency sequence ready.
$bc = new backup_controller(
backup::TYPE_1COURSE,
$course->id,
backup::FORMAT_MOODLE,
backup::INTERACTIVE_NO,
backup::MODE_IMPORT,
$USER->id);
$tasks = $bc->get_plan()->get_tasks();
foreach ($tasks as $task) {
// We need only the task to backup section 1.
if ($task instanceof backup_section_task && $task->get_name() == "1") {
$task->add_step($step);
break;
}
}
$reflection = new \ReflectionClass($step);
$method = $reflection->getMethod('define_structure');
$structure = $method->invoke($step);
$bc->destroy();
$elements = $structure->get_final_elements();
$this->assertArrayHasKey('number', $elements);
$this->assertArrayHasKey('name', $elements);
$this->assertArrayHasKey('summary', $elements);
$this->assertArrayHasKey('summaryformat', $elements);
$this->assertArrayHasKey('sequence', $elements);
$this->assertArrayHasKey('visible', $elements);
$this->assertArrayHasKey('availabilityjson', $elements);
$this->assertArrayHasKey('component', $elements);
$this->assertArrayHasKey('itemid', $elements);
$this->assertArrayHasKey('timemodified', $elements);
}
}
@@ -0,0 +1,96 @@
<?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_xml_transformer;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
require_once($CFG->dirroot . '/backup/moodle2/backup_plan_builder.class.php');
/**
* Tests for backup_xml_transformer.
*
* @package core_backup
* @subpackage moodle2
* @category test
* @copyright 2017 Dmitrii Metelkin (dmitriim@catalyst-au.net)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class backup_xml_transformer_test extends \advanced_testcase {
/**
* Initial set up.
*/
public function setUp(): void {
parent::setUp();
$this->resetAfterTest(true);
}
/**
* Data provider for ::test_filephp_links_replace.
*
* @return array
*/
public function filephp_links_replace_data_provider() {
return array(
array('http://test.test/', 'http://test.test/'),
array('http://test.test/file.php/1', 'http://test.test/file.php/1'),
array('http://test.test/file.php/2/1.jpg', 'http://test.test/file.php/2/1.jpg'),
array('http://test.test/file.php/2', 'http://test.test/file.php/2'),
array('http://test.test/file.php/1/1.jpg', '$@FILEPHP@$$@SLASH@$1.jpg'),
array('http://test.test/file.php/1//1.jpg', '$@FILEPHP@$$@SLASH@$$@SLASH@$1.jpg'),
array('http://test.test/file.php?file=/1', '$@FILEPHP@$'),
array('http://test.test/file.php?file=/2/1.jpg', 'http://test.test/file.php?file=/2/1.jpg'),
array('http://test.test/file.php?file=/2', 'http://test.test/file.php?file=/2'),
array('http://test.test/file.php?file=/1/1.jpg', '$@FILEPHP@$$@SLASH@$1.jpg'),
array('http://test.test/file.php?file=/1//1.jpg', '$@FILEPHP@$$@SLASH@$$@SLASH@$1.jpg'),
array('http://test.test/file.php?file=%2f1', '$@FILEPHP@$'),
array('http://test.test/file.php?file=%2f2%2f1.jpg', 'http://test.test/file.php?file=%2f2%2f1.jpg'),
array('http://test.test/file.php?file=%2f2', 'http://test.test/file.php?file=%2f2'),
array('http://test.test/file.php?file=%2f1%2f1.jpg', '$@FILEPHP@$$@SLASH@$1.jpg'),
array('http://test.test/file.php?file=%2f1%2f%2f1.jpg', '$@FILEPHP@$$@SLASH@$$@SLASH@$1.jpg'),
array('http://test.test/file.php?file=%2F1', '$@FILEPHP@$'),
array('http://test.test/file.php?file=%2F2%2F1.jpg', 'http://test.test/file.php?file=%2F2%2F1.jpg'),
array('http://test.test/file.php?file=%2F2', 'http://test.test/file.php?file=%2F2'),
array('http://test.test/file.php?file=%2F1%2F1.jpg', '$@FILEPHP@$$@SLASH@$1.jpg'),
array('http://test.test/file.php?file=%2F1%2F%2F1.jpg', '$@FILEPHP@$$@SLASH@$$@SLASH@$1.jpg'),
array('http://test.test/h5p/embed.php?url=testurl', '$@H5PEMBED@$?url=testurl'),
);
}
/**
* Test that backup_xml_transformer replaces file php links to $@FILEPHP@$.
*
* @dataProvider filephp_links_replace_data_provider
* @param string $content Testing content.
* @param string $expected Expected result.
*/
public function test_filephp_links_replace($content, $expected): void {
global $CFG;
$CFG->wwwroot = 'http://test.test';
$transformer = new backup_xml_transformer(1);
$this->assertEquals($expected, $transformer->process($content));
}
}
@@ -0,0 +1,26 @@
@core @core_backup
Feature: Backup and restore of the question that was tagged
Background:
Given the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following config values are set as admin:
| enableasyncbackup | 0 |
@javascript @_file_upload
Scenario: Restore the quiz containing the question that was tagged
Given I am on the "Course 1" "restore" page logged in as "admin"
And I press "Manage course backups"
And I upload "backup/moodle2/tests/fixtures/test_tags_backup.mbz" file to "Files" filemanager
And I press "Save changes"
And I restore "test_tags_backup.mbz" backup into a new course using this options:
| Schema | Course name | Course 2 |
| Schema | Course short name | C2 |
When I am on the "TF1" "core_question > edit" page logged in as admin
And I expand all fieldsets
Then I should see "Tag1-TF1"
And I should see "Tag2-TF1"
And I am on the "TF2" "core_question > edit" page logged in as admin
And I expand all fieldsets
And I should see "Tag1-TF2"
@@ -0,0 +1,40 @@
@core @core_backup
Feature: Import course's content's twice
In order to import content from a course more than one
As a teacher
I need to confirm that errors will not happen
Background:
Given the following config values are set as admin:
| enableglobalsearch | 1 |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
| Course 2 | C2 | 0 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| teacher1 | C2 | editingteacher |
And the following "blocks" exist:
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
| online_users | Course | C1 | course-view-* | site-post |
And the following "activities" exist:
| activity | name | course | idnumber |
| quiz | Test quiz | C1 | quiz1 |
And I log in as "teacher1"
Scenario: Import course's contents to another course
Given I am on "Course 2" course homepage
And I should not see "Online users"
And I should not see "Test quiz"
And I import "Course 1" course into "Course 2" course using this options:
And I am on "Course 2" course homepage
And I should see "Online users"
And I should see "Test quiz"
When I import "Course 1" course into "Course 2" course using this options:
And I am on "Course 2" course homepage
Then I should see "Online users"
And I should see "Test quiz"
Binary file not shown.
@@ -0,0 +1,82 @@
<?php
// This file is part of Moodle - https://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 <https://www.gnu.org/licenses/>.
/**
* Various fixture course formats for backup unit tests
*
* @package core_backup
* @category test
* @copyright 2022 onwards Eloy Lafuente (stronk7) {@link https://stronk7.com}
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/course/format/topics/lib.php');
/**
* Fixture course format with one option.
*/
class format_test_cs_options extends format_topics {
/**
* Override method format_topics::get_default_section_name to prevent PHPUnit errors related to the nonexistent
* format_test_cs_options lang file.
*
* @param \stdClass $section The section in question.
* @return string The section's name for display.
*/
public function get_default_section_name($section) {
if ($section->section == 0) {
return parent::get_default_section_name($section);
} else {
return get_string('sectionname', 'format_topics') . ' ' . $section->section;
}
}
public function section_format_options($foreditform = false) {
return array(
'numdaystocomplete' => array(
'type' => PARAM_INT,
'label' => 'Test days',
'element_type' => 'text',
'default' => 0,
),
);
}
}
/**
* Fixture course format with 2 options, 1 inherited.
*/
class format_test_cs2_options extends format_test_cs_options {
public function section_format_options($foreditform = false) {
return array(
'numdaystocomplete' => array(
'type' => PARAM_INT,
'label' => 'Test days',
'element_type' => 'text',
'default' => 0,
),
'secondparameter' => array(
'type' => PARAM_INT,
'label' => 'Test Parmater',
'element_type' => 'text',
'default' => 0,
),
) + parent::section_format_options($foreditform);
}
}
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,10 @@
<gradebook >
<attributes>
<calculations_freeze>20160511</calculations_freeze>
</attributes>
<grade_categories>
<grade_category id="10">
<depth>1</depth>
</grade_category>
</grade_categories>
</gradebook>
@@ -0,0 +1,7 @@
<gradebook calculations_freeze="20160511">
<grade_categories>
<grade_category id="10">
<depth>1</depth>
</grade_category>
</grade_categories>
</gradebook>
@@ -0,0 +1,10 @@
<gradebook some_other_value="false" >
<attributes>
<calculations_freeze>20160511</calculations_freeze>
</attributes>
<grade_categories>
<grade_category id="10">
<depth>1</depth>
</grade_category>
</grade_categories>
</gradebook>
@@ -0,0 +1,7 @@
<gradebook some_other_value="false" calculations_freeze="20160511">
<grade_categories>
<grade_category id="10">
<depth>1</depth>
</grade_category>
</grade_categories>
</gradebook>
@@ -0,0 +1,10 @@
<gradebook some_other_value="false" and_another_value="42">
<attributes>
<calculations_freeze>20160511</calculations_freeze>
</attributes>
<grade_categories>
<grade_category id="10">
<depth>1</depth>
</grade_category>
</grade_categories>
</gradebook>
@@ -0,0 +1,7 @@
<gradebook some_other_value="false" calculations_freeze="20160511" and_another_value="42">
<grade_categories>
<grade_category id="10">
<depth>1</depth>
</grade_category>
</grade_categories>
</gradebook>
@@ -0,0 +1,7 @@
<gradebookplugin some_other_value="false" calculations_freeze="20160511" and_another_value="42">
<grade_categories>
<grade_category id="10">
<depth>1</depth>
</grade_category>
</grade_categories>
</gradebookplugin>
@@ -0,0 +1,7 @@
<gradebookplugin some_other_value="false" calculations_freeze="20160511" and_another_value="42">
<grade_categories>
<grade_category id="10">
<depth>1</depth>
</grade_category>
</grade_categories>
</gradebookplugin>
Binary file not shown.
@@ -0,0 +1,208 @@
<?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 restore_dbops;
use restore_controller;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
require_once($CFG->dirroot . '/course/format/topics/lib.php');
require_once($CFG->libdir . '/completionlib.php');
require_once($CFG->dirroot . '/backup/moodle2/tests/fixtures/format_test_cs_options.php');
/**
* Tests for Moodle 2 course format section_options backup operation.
*
* @package core_backup
* @copyright 2014 Russell Smith
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class moodle2_course_format_test extends \advanced_testcase {
/**
* Tests a backup and restore adds the required section option data
* when the same course format is used.
*/
public function test_course_format_options_restore(): void {
global $DB, $CFG;
$this->resetAfterTest(true);
$this->setAdminUser();
$CFG->enableavailability = true;
$CFG->enablecompletion = true;
// Create a course with some availability data set.
$generator = $this->getDataGenerator();
$course = $generator->create_course(
array('format' => 'test_cs_options', 'numsections' => 3,
'enablecompletion' => COMPLETION_ENABLED),
array('createsections' => true));
$courseobject = \core_courseformat\base::instance($course->id);
$section = $DB->get_record('course_sections',
array('course' => $course->id, 'section' => 1), '*', MUST_EXIST);
$data = array('id' => $section->id,
'numdaystocomplete' => 2);
$courseobject->update_section_format_options($data);
// Backup and restore it.
$this->backup_and_restore($course);
$sectionoptions = $courseobject->get_format_options(1);
$this->assertArrayHasKey('numdaystocomplete', $sectionoptions);
$this->assertEquals(2, $sectionoptions['numdaystocomplete']);
}
/**
* Tests an import into the same subject successfully
* restores the options without error.
*/
public function test_course_format_options_import_myself(): void {
global $DB, $CFG;
$this->resetAfterTest(true);
$this->setAdminUser();
$CFG->enableavailability = true;
$CFG->enablecompletion = true;
// Create a course with some availability data set.
$generator = $this->getDataGenerator();
$course = $generator->create_course(
array('format' => 'test_cs_options', 'numsections' => 3,
'enablecompletion' => COMPLETION_ENABLED),
array('createsections' => true));
$courseobject = \core_courseformat\base::instance($course->id);
$section = $DB->get_record('course_sections',
array('course' => $course->id, 'section' => 1), '*', MUST_EXIST);
$data = array('id' => $section->id,
'numdaystocomplete' => 2);
$courseobject->update_section_format_options($data);
$this->backup_and_restore($course, $course, backup::TARGET_EXISTING_ADDING);
$sectionoptions = $courseobject->get_format_options(1);
$this->assertArrayHasKey('numdaystocomplete', $sectionoptions);
$this->assertArrayNotHasKey('secondparameter', $sectionoptions);
$this->assertEquals(2, $sectionoptions['numdaystocomplete']);
}
/**
* Tests that all section options are copied when the course format is changed.
* None of the data is copied.
*
* It is a future enhancement to copy;
* 1. Only the relevant options.
* 2. Only the data associated with relevant options.
*/
public function test_course_format_options_restore_new_format(): void {
global $DB, $CFG;
$this->resetAfterTest(true);
$this->setAdminUser();
// Create a source course using the test_cs2_options format.
$generator = $this->getDataGenerator();
$course = $generator->create_course(
array('format' => 'test_cs2_options', 'numsections' => 3,
'enablecompletion' => COMPLETION_ENABLED),
array('createsections' => true));
// Create a target course using test_cs_options format.
$newcourse = $generator->create_course(
array('format' => 'test_cs_options', 'numsections' => 3,
'enablecompletion' => COMPLETION_ENABLED),
array('createsections' => true));
// Set section 2 to have both options, and a name.
$courseobject = \core_courseformat\base::instance($course->id);
$section = $DB->get_record('course_sections',
array('course' => $course->id, 'section' => 2), '*', MUST_EXIST);
$data = array('id' => $section->id,
'numdaystocomplete' => 2,
'secondparameter' => 8
);
$courseobject->update_section_format_options($data);
$DB->set_field('course_sections', 'name', 'Frogs', array('id' => $section->id));
// Backup and restore to the new course using 'add to existing' so it
// keeps the current (test_cs_options) format.
$this->backup_and_restore($course, $newcourse, backup::TARGET_EXISTING_ADDING);
// Check that the section contains the options suitable for the new
// format and that even the one with the same name as from the old format
// has NOT been set.
$newcourseobject = \core_courseformat\base::instance($newcourse->id);
$sectionoptions = $newcourseobject->get_format_options(2);
$this->assertArrayHasKey('numdaystocomplete', $sectionoptions);
$this->assertArrayNotHasKey('secondparameter', $sectionoptions);
$this->assertEquals(0, $sectionoptions['numdaystocomplete']);
// However, the name should have been changed, as this does not depend
// on the format.
$modinfo = get_fast_modinfo($newcourse->id);
$section = $modinfo->get_section_info(2);
$this->assertEquals('Frogs', $section->name);
}
/**
* Backs a course up and restores it.
*
* @param \stdClass $srccourse Course object to backup
* @param \stdClass $dstcourse Course object to restore into
* @param int $target Target course mode (backup::TARGET_xx)
* @return int ID of newly restored course
*/
protected function backup_and_restore($srccourse, $dstcourse = null,
$target = backup::TARGET_NEW_COURSE) {
global $USER, $CFG;
// Turn off file logging, otherwise it can't delete the file (Windows).
$CFG->backup_file_logger_level = backup::LOG_NONE;
// Do backup with default settings. MODE_IMPORT means it will just
// create the directory and not zip it.
$bc = new backup_controller(backup::TYPE_1COURSE, $srccourse->id,
backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_IMPORT,
$USER->id);
$backupid = $bc->get_backupid();
$bc->execute_plan();
$bc->destroy();
// Do restore to new course with default settings.
if ($dstcourse !== null) {
$newcourseid = $dstcourse->id;
} else {
$newcourseid = restore_dbops::create_new_course(
$srccourse->fullname, $srccourse->shortname . '_2', $srccourse->category);
}
$rc = new restore_controller($backupid, $newcourseid,
backup::INTERACTIVE_NO, backup::MODE_GENERAL, $USER->id,
$target);
$this->assertTrue($rc->execute_precheck());
$rc->execute_plan();
$rc->destroy();
return $newcourseid;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,85 @@
<?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;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
require_once($CFG->libdir . '/completionlib.php');
/**
* Test for restore_stepslib.
*
* @package core_backup
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class restore_gradebook_structure_step_test extends \advanced_testcase {
/**
* Provide tests for rewrite_step_backup_file_for_legacy_freeze based upon fixtures.
*
* @return array
*/
public function rewrite_step_backup_file_for_legacy_freeze_provider() {
$fixturesdir = realpath(__DIR__ . '/fixtures/rewrite_step_backup_file_for_legacy_freeze/');
$tests = [];
$iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($fixturesdir),
\RecursiveIteratorIterator::LEAVES_ONLY);
foreach ($iterator as $sourcefile) {
$pattern = '/\.test$/';
if (!preg_match($pattern, $sourcefile)) {
continue;
}
$expectfile = preg_replace($pattern, '.expectation', $sourcefile);
$test = array($sourcefile, $expectfile);
$tests[basename($sourcefile)] = $test;
}
return $tests;
}
/**
* @dataProvider rewrite_step_backup_file_for_legacy_freeze_provider
* @param string $source The source file to test
* @param string $expected The expected result of the transformation
*/
public function test_rewrite_step_backup_file_for_legacy_freeze($source, $expected): void {
$restore = $this->getMockBuilder('\restore_gradebook_structure_step')
->onlyMethods([])
->disableOriginalConstructor()
->getMock()
;
// Copy the file somewhere as the rewrite_step_backup_file_for_legacy_freeze will write the file.
$dir = make_request_directory(true);
$filepath = $dir . DIRECTORY_SEPARATOR . 'file.xml';
copy($source, $filepath);
$rc = new \ReflectionClass('\restore_gradebook_structure_step');
$rcm = $rc->getMethod('rewrite_step_backup_file_for_legacy_freeze');
$rcm->invoke($restore, $filepath);
// Check the result.
$this->assertFileEquals($expected, $filepath);
}
}
@@ -0,0 +1,445 @@
<?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 mod_quiz\quiz_attempt;
use mod_quiz\quiz_settings;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->libdir . "/phpunit/classes/restore_date_testcase.php");
require_once($CFG->libdir . "/badgeslib.php");
require_once($CFG->dirroot . '/mod/assign/tests/base_test.php');
/**
* Restore date tests.
*
* @package core_backup
* @copyright 2017 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class restore_stepslib_date_test extends \restore_date_testcase {
/**
* Restoring a manual grade item does not result in the timecreated or
* timemodified dates being changed.
*/
public function test_grade_item_date_restore(): void {
$course = $this->getDataGenerator()->create_course(['startdate' => time()]);
$params = new \stdClass();
$params->courseid = $course->id;
$params->fullname = 'unittestgradecalccategory';
$params->aggregation = GRADE_AGGREGATE_MEAN;
$params->aggregateonlygraded = 0;
$gradecategory = new \grade_category($params, false);
$gradecategory->insert();
$gradecategory->load_grade_item();
$gradeitems = new \grade_item();
$gradeitems->courseid = $course->id;
$gradeitems->categoryid = $gradecategory->id;
$gradeitems->itemname = 'manual grade_item';
$gradeitems->itemtype = 'manual';
$gradeitems->itemnumber = 0;
$gradeitems->needsupdate = false;
$gradeitems->gradetype = GRADE_TYPE_VALUE;
$gradeitems->grademin = 0;
$gradeitems->grademax = 10;
$gradeitems->iteminfo = 'Manual grade item used for unit testing';
$gradeitems->timecreated = time();
$gradeitems->timemodified = time();
$gradeitems->aggregationcoef = GRADE_AGGREGATE_SUM;
$gradeitems->insert();
$gradeitemparams = [
'itemtype' => 'manual',
'itemname' => $gradeitems->itemname,
'courseid' => $course->id,
];
$gradeitem = \grade_item::fetch($gradeitemparams);
// Do backup and restore.
$newcourseid = $this->backup_and_restore($course);
$newcourse = get_course($newcourseid);
$newgradeitemparams = [
'itemtype' => 'manual',
'itemname' => $gradeitems->itemname,
'courseid' => $course->id,
];
$newgradeitem = \grade_item::fetch($newgradeitemparams);
$this->assertEquals($gradeitem->timecreated, $newgradeitem->timecreated);
$this->assertEquals($gradeitem->timemodified, $newgradeitem->timemodified);
}
/**
* The course section timemodified date does not get rolled forward
* when the course is restored.
*/
public function test_course_section_date_restore(): void {
global $DB;
// Create a course.
$course = $this->getDataGenerator()->create_course(['startdate' => time()]);
// Get the second course section.
$section = $DB->get_record('course_sections', ['course' => $course->id, 'section' => '1']);
// Do a backup and restore.
$newcourseid = $this->backup_and_restore($course);
$newcourse = get_course($newcourseid);
$newsection = $DB->get_record('course_sections', ['course' => $newcourse->id, 'section' => '1']);
// Compare dates.
$this->assertEquals($section->timemodified, $newsection->timemodified);
}
/**
* Test that the timecreated and timemodified dates are not rolled forward when restoring
* badge data.
*/
public function test_badge_date_restore(): void {
global $DB, $USER;
// Create a course.
$course = $this->getDataGenerator()->create_course(['startdate' => time()]);
// Create a badge.
$fordb = new \stdClass();
$fordb->id = null;
$fordb->name = "Test badge";
$fordb->description = "Testing badges";
$fordb->timecreated = time();
$fordb->timemodified = time();
$fordb->usercreated = $USER->id;
$fordb->usermodified = $USER->id;
$fordb->issuername = "Test issuer";
$fordb->issuerurl = "http://issuer-url.domain.co.nz";
$fordb->issuercontact = "issuer@example.com";
$fordb->expiredate = time();
$fordb->expireperiod = null;
$fordb->type = BADGE_TYPE_COURSE;
$fordb->courseid = $course->id;
$fordb->messagesubject = "Test message subject";
$fordb->message = "Test message body";
$fordb->attachment = 1;
$fordb->notification = 0;
$fordb->status = BADGE_STATUS_INACTIVE;
$fordb->nextcron = time();
$DB->insert_record('badge', $fordb, true);
// Do a backup and restore.
$newcourseid = $this->backup_and_restore($course);
$newcourse = get_course($newcourseid);
$badges = badges_get_badges(BADGE_TYPE_COURSE, $newcourseid);
// Compare dates.
$badge = array_shift($badges);
$this->assertEquals($fordb->timecreated, $badge->timecreated);
$this->assertEquals($fordb->timemodified, $badge->timemodified);
$this->assertEquals($fordb->nextcron, $badge->nextcron);
// Expire date should be moved forward.
$this->assertNotEquals($fordb->expiredate, $badge->expiredate);
}
/**
* Test that course calendar events timemodified field is not rolled forward
* when restoring the course.
*/
public function test_calendarevents_date_restore(): void {
global $USER, $DB;
// Create course.
$course = $this->getDataGenerator()->create_course(['startdate' => time()]);
// Create calendar event.
$starttime = time();
$event = [
'name' => 'Start of assignment',
'description' => '',
'format' => 1,
'courseid' => $course->id,
'groupid' => 0,
'userid' => $USER->id,
'modulename' => 0,
'instance' => 0,
'eventtype' => 'course',
'timestart' => $starttime,
'timeduration' => 86400,
'visible' => 1
];
$calendarevent = \calendar_event::create($event, false);
// Backup and restore.
$newcourseid = $this->backup_and_restore($course);
$newcourse = get_course($newcourseid);
$newevent = $DB->get_record('event', ['courseid' => $newcourseid, 'eventtype' => 'course']);
// Compare dates.
$this->assertEquals($calendarevent->timemodified, $newevent->timemodified);
$this->assertNotEquals($calendarevent->timestart, $newevent->timestart);
}
/**
* Testing that the timeenrolled, timestarted, and timecompleted fields are not rolled forward / back
* when doing a course restore.
*/
public function test_course_completion_date_restore(): void {
global $DB;
// Create course with course completion enabled.
$course = $this->getDataGenerator()->create_course(['startdate' => time(), 'enablecompletion' => 1]);
// Enrol a user in the course.
$user = $this->getDataGenerator()->create_user();
$studentrole = $DB->get_record('role', ['shortname' => 'student']);
$this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id);
// Complete the course with a user.
$ccompletion = new \completion_completion(['course' => $course->id,
'userid' => $user->id,
'timeenrolled' => time(),
'timestarted' => time()
]);
// Now, mark the course as completed.
$ccompletion->mark_complete();
$this->assertEquals('100', \core_completion\progress::get_course_progress_percentage($course, $user->id));
// Back up and restore.
$newcourseid = $this->backup_and_restore($course);
$newcourse = get_course($newcourseid);
$newcompletion = \completion_completion::fetch(['course' => $newcourseid, 'userid' => $user->id]);
// Compare dates.
$this->assertEquals($ccompletion->timeenrolled, $newcompletion->timeenrolled);
$this->assertEquals($ccompletion->timestarted, $newcompletion->timestarted);
$this->assertEquals($ccompletion->timecompleted, $newcompletion->timecompleted);
}
/**
* Testing that the grade grade date information is not changed in the gradebook when a course
* restore is performed.
*/
public function test_grade_grade_date_restore(): void {
global $USER, $DB;
// Testing the restore of an overridden grade.
list($course, $assign) = $this->create_course_and_module('assign', []);
$cm = $DB->get_record('course_modules', ['course' => $course->id, 'instance' => $assign->id]);
$assignobj = new \mod_assign_testable_assign(\context_module::instance($cm->id), $cm, $course);
$submission = $assignobj->get_user_submission($USER->id, true);
$grade = $assignobj->get_user_grade($USER->id, true);
$grade->grade = 75;
$assignobj->update_grade($grade);
// Find the grade item.
$gradeitemparams = [
'itemtype' => 'mod',
'iteminstance' => $assign->id,
'itemmodule' => 'assign',
'courseid' => $course->id,
];
$gradeitem = \grade_item::fetch($gradeitemparams);
// Next the grade grade.
$gradegrade = \grade_grade::fetch(['itemid' => $gradeitem->id, 'userid' => $USER->id]);
$gradegrade->set_overridden(true);
// Back up and restore.
$newcourseid = $this->backup_and_restore($course);
$newcourse = get_course($newcourseid);
// Find assignment.
$assignid = $DB->get_field('assign', 'id', ['course' => $newcourseid]);
// Find grade item.
$newgradeitemparams = [
'itemtype' => 'mod',
'iteminstance' => $assignid,
'itemmodule' => 'assign',
'courseid' => $newcourse->id,
];
$newgradeitem = \grade_item::fetch($newgradeitemparams);
// Find grade grade.
$newgradegrade = \grade_grade::fetch(['itemid' => $newgradeitem->id, 'userid' => $USER->id]);
// Compare dates.
$this->assertEquals($gradegrade->timecreated, $newgradegrade->timecreated);
$this->assertEquals($gradegrade->timemodified, $newgradegrade->timemodified);
$this->assertEquals($gradegrade->overridden, $newgradegrade->overridden);
}
/**
* Checking that the user completion of an activity relating to the timemodified field does not change
* when doing a course restore.
*/
public function test_usercompletion_date_restore(): void {
global $USER, $DB;
// More completion...
$course = $this->getDataGenerator()->create_course(['startdate' => time(), 'enablecompletion' => 1]);
$assign = $this->getDataGenerator()->create_module('assign', [
'course' => $course->id,
'completion' => COMPLETION_TRACKING_AUTOMATIC, // Show activity as complete when conditions are met.
'completionusegrade' => 1 // Student must receive a grade to complete this activity.
]);
$cm = $DB->get_record('course_modules', ['course' => $course->id, 'instance' => $assign->id]);
$assignobj = new \mod_assign_testable_assign(\context_module::instance($cm->id), $cm, $course);
$submission = $assignobj->get_user_submission($USER->id, true);
$grade = $assignobj->get_user_grade($USER->id, true);
$grade->grade = 75;
$assignobj->update_grade($grade);
$coursemodulecompletion = $DB->get_record('course_modules_completion', ['coursemoduleid' => $cm->id]);
// Back up and restore.
$newcourseid = $this->backup_and_restore($course);
$newcourse = get_course($newcourseid);
// Find assignment.
$assignid = $DB->get_field('assign', 'id', ['course' => $newcourseid]);
$cm = $DB->get_record('course_modules', ['course' => $newcourse->id, 'instance' => $assignid]);
$newcoursemodulecompletion = $DB->get_record('course_modules_completion', ['coursemoduleid' => $cm->id]);
$this->assertEquals($coursemodulecompletion->timemodified, $newcoursemodulecompletion->timemodified);
}
/**
* Checking that the user completion of an activity relating to the view field does not change
* when doing a course restore.
* @covers ::backup_and_restore
*/
public function test_usercompletion_view_restore(): void {
global $DB;
// More completion...
$course = $this->getDataGenerator()->create_course(['startdate' => time(), 'enablecompletion' => 1]);
$student = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($student->id, $course->id, 'student');
$assign = $this->getDataGenerator()->create_module('assign', [
'course' => $course->id,
'completion' => COMPLETION_TRACKING_AUTOMATIC, // Show activity as complete when conditions are met.
'completionview' => 1
]);
$cm = $DB->get_record('course_modules', ['course' => $course->id, 'instance' => $assign->id]);
// Mark the activity as completed.
$completion = new \completion_info($course);
$completion->set_module_viewed($cm, $student->id);
$coursemodulecompletion = $DB->get_record('course_modules_viewed', ['coursemoduleid' => $cm->id]);
// Back up and restore.
$newcourseid = $this->backup_and_restore($course);
$newcourse = get_course($newcourseid);
$assignid = $DB->get_field('assign', 'id', ['course' => $newcourseid]);
$cm = $DB->get_record('course_modules', ['course' => $newcourse->id, 'instance' => $assignid]);
$newcoursemodulecompletion = $DB->get_record('course_modules_viewed', ['coursemoduleid' => $cm->id]);
$this->assertEquals($coursemodulecompletion->timecreated, $newcoursemodulecompletion->timecreated);
}
/**
* Ensuring that the timemodified field of the question attempt steps table does not change when
* a course restore is done.
*/
public function test_question_attempt_steps_date_restore(): void {
global $DB;
$course = $this->getDataGenerator()->create_course(['startdate' => time()]);
// Make a quiz.
$quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
$quiz = $quizgenerator->create_instance(array('course' => $course->id, 'questionsperpage' => 0, 'grade' => 100.0,
'sumgrades' => 2));
$cm = $DB->get_record('course_modules', ['course' => $course->id, 'instance' => $quiz->id]);
// Create a couple of questions.
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
$cat = $questiongenerator->create_question_category();
$saq = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id));
$numq = $questiongenerator->create_question('numerical', null, array('category' => $cat->id));
// Add them to the quiz.
quiz_add_quiz_question($saq->id, $quiz);
quiz_add_quiz_question($numq->id, $quiz);
// Make a user to do the quiz.
$user1 = $this->getDataGenerator()->create_user();
$quizobj = quiz_settings::create($quiz->id, $user1->id);
// Start the attempt.
$quba = \question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
$quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
$timenow = time();
$attempt = quiz_create_attempt($quizobj, 1, false, $timenow, false, $user1->id);
quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow);
quiz_attempt_save_started($quizobj, $quba, $attempt);
// Process some responses from the student.
$attemptobj = quiz_attempt::create($attempt->id);
$prefix1 = $quba->get_field_prefix(1);
$prefix2 = $quba->get_field_prefix(2);
$tosubmit = array(1 => array('answer' => 'frog'),
2 => array('answer' => '3.14'));
$attemptobj->process_submitted_actions($timenow, false, $tosubmit);
// Finish the attempt.
$attemptobj = quiz_attempt::create($attempt->id);
$attemptobj->process_finish($timenow, false);
$questionattemptstepdates = [];
$originaliterator = $quba->get_attempt_iterator();
foreach ($originaliterator as $questionattempt) {
$questionattemptstepdates[] = ['originaldate' => $questionattempt->get_last_action_time()];
}
// Back up and restore.
$newcourseid = $this->backup_and_restore($course);
$newcourse = get_course($newcourseid);
// Get the quiz for this new restored course.
$quizdata = $DB->get_record('quiz', ['course' => $newcourseid]);
$quizobj = \mod_quiz\quiz_settings::create($quizdata->id, $user1->id);
$questionusage = $DB->get_record('question_usages', [
'component' => 'mod_quiz',
'contextid' => $quizobj->get_context()->id
]);
$newquba = \question_engine::load_questions_usage_by_activity($questionusage->id);
$restorediterator = $newquba->get_attempt_iterator();
$i = 0;
foreach ($restorediterator as $restoredquestionattempt) {
$questionattemptstepdates[$i]['restoredate'] = $restoredquestionattempt->get_last_action_time();
$i++;
}
foreach ($questionattemptstepdates as $dates) {
$this->assertEquals($dates['originaldate'], $dates['restoredate']);
}
}
}
@@ -0,0 +1,133 @@
<?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;
/**
* Tests for Moodle 2 restore steplib classes.
*
* @package core_backup
* @copyright 2023 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class restore_stepslib_test extends \advanced_testcase {
/**
* Setup to include all libraries.
*/
public static function setUpBeforeClass(): void {
global $CFG;
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
require_once($CFG->dirroot . '/backup/moodle2/restore_stepslib.php');
}
/**
* Makes a backup of the course.
*
* @param \stdClass $course The course object.
* @return string Unique identifier for this backup.
*/
protected function backup_course(\stdClass $course): string {
global $CFG, $USER;
// Turn off file logging, otherwise it can't delete the file (Windows).
$CFG->backup_file_logger_level = backup::LOG_NONE;
// Do backup with default settings. MODE_IMPORT means it will just
// create the directory and not zip it.
$bc = new \backup_controller(
backup::TYPE_1COURSE,
$course->id,
backup::FORMAT_MOODLE,
backup::INTERACTIVE_NO,
backup::MODE_IMPORT,
$USER->id
);
$backupid = $bc->get_backupid();
$bc->execute_plan();
$bc->destroy();
return $backupid;
}
/**
* Restores a backup that has been made earlier.
*
* @param string $backupid The unique identifier of the backup.
* @return int The new course id.
*/
protected function restore_replacing_content(string $backupid): int {
global $CFG, $USER;
// Create course to restore into, and a user to do the restore.
$generator = $this->getDataGenerator();
$course = $generator->create_course();
// Turn off file logging, otherwise it can't delete the file (Windows).
$CFG->backup_file_logger_level = backup::LOG_NONE;
// Do restore to new course with default settings.
$rc = new \restore_controller(
$backupid,
$course->id,
backup::INTERACTIVE_NO,
backup::MODE_GENERAL,
$USER->id,
backup::TARGET_EXISTING_DELETING
);
$precheck = $rc->execute_precheck();
$this->assertTrue($precheck);
$rc->get_plan()->get_setting('role_assignments')->set_value(true);
$rc->get_plan()->get_setting('permissions')->set_value(true);
$rc->execute_plan();
$rc->destroy();
return $course->id;
}
/**
* Test for the section structure step included elements.
*
* @covers \restore_section_structure_step::process_section
*/
public function test_restore_section_structure_step(): void {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course(['numsections' => 2, 'format' => 'topics']);
$backupid = $this->backup_course($course);
$newcourseid = $this->restore_replacing_content($backupid);
$originalsections = get_fast_modinfo($course->id)->get_section_info_all();
$restoredsections = get_fast_modinfo($newcourseid)->get_section_info_all();
$this->assertEquals(count($originalsections), count($restoredsections));
$validatefields = ['name', 'summary', 'summaryformat', 'visible', 'component', 'itemid'];
foreach ($validatefields as $field) {
$this->assertEquals($originalsections[1]->$field, $restoredsections[1]->$field);
$this->assertEquals($originalsections[2]->$field, $restoredsections[2]->$field);
}
}
}