From c8628576e3ea4cf46c55bd4e5503a4d8d2548c31 Mon Sep 17 00:00:00 2001 From: Sara Arjona Date: Tue, 21 Feb 2023 16:46:05 +0100 Subject: [PATCH 1/2] MDL-77254 h5p: Add support to restored states The restored states should be treated differently, to avoid resetting them by default the first time users access them. In that case, the first time users access to content with restored xAPI states, they should be displayed (instead of resetting them). --- h5p/classes/framework.php | 4 +++- h5p/classes/player.php | 24 ++++++++++++++++++++++++ lib/xapi/classes/local/state.php | 2 +- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/h5p/classes/framework.php b/h5p/classes/framework.php index b87a54bd0dc..33cba574f32 100644 --- a/h5p/classes/framework.php +++ b/h5p/classes/framework.php @@ -960,7 +960,9 @@ class framework implements H5PFrameworkInterface { // Reset user data. try { $xapihandler = handler::create($file->get_component()); - $xapihandler->reset_states($file->get_contextid()); + // Reset only entries with 'state' as stateid (the ones restored shouldn't be restored, because the H5P + // content hasn't been created yet). + $xapihandler->reset_states($file->get_contextid(), null, 'state'); } catch (xapi_exception $exception) { // This component doesn't support xAPI State, so no content needs to be reset. return; diff --git a/h5p/classes/player.php b/h5p/classes/player.php index 3492ffe1340..e8b12b45e18 100644 --- a/h5p/classes/player.php +++ b/h5p/classes/player.php @@ -277,6 +277,30 @@ class player { ); try { $state = $xapihandler->load_state($state); + if (!$state) { + // Check if the state has been restored from a backup for the current user. + $state = new state( + item_agent::create_from_user($USER), + $xapiobject, + 'restored', + null, + null + ); + $state = $xapihandler->load_state($state); + if ($state && !is_null($state->get_state_data())) { + // A restored state has been found. It will be replaced with one with the proper stateid and statedata. + $xapihandler->delete_state($state); + $state = new state( + item_agent::create_from_user($USER), + $xapiobject, + 'state', + $state->jsonSerialize(), + null + ); + $xapihandler->save_state($state); + } + } + if (!$state) { return $emptystatedata; } diff --git a/lib/xapi/classes/local/state.php b/lib/xapi/classes/local/state.php index 1850e13a142..ace985b02b7 100644 --- a/lib/xapi/classes/local/state.php +++ b/lib/xapi/classes/local/state.php @@ -72,7 +72,7 @@ class state implements JsonSerializable { /** * Return the data to serialize in case JSON state when needed. * - * @return stdClass The state data structure + * @return stdClass The state data structure. If statedata is null, this method will return an empty class. */ public function jsonSerialize(): stdClass { if ($this->statedata) { From 1996a7cfbff7b77a4066e655288143fced4f6581 Mon Sep 17 00:00:00 2001 From: Sara Arjona Date: Mon, 20 Feb 2023 16:15:39 +0100 Subject: [PATCH 2/2] MDL-77254 backup: Backup/restore xAPI state --- admin/settings/courses.php | 15 +++ backup/moodle2/backup_activity_task.class.php | 5 + backup/moodle2/backup_root_task.class.php | 6 + backup/moodle2/backup_settingslib.php | 6 + backup/moodle2/backup_stepslib.php | 32 ++++++ .../moodle2/restore_activity_task.class.php | 5 + backup/moodle2/restore_root_task.class.php | 12 ++ backup/moodle2/restore_settingslib.php | 6 + backup/moodle2/restore_stepslib.php | 55 ++++++++++ backup/moodle2/tests/moodle2_test.php | 85 ++++++++++++++- .../dbops/backup_controller_dbops.class.php | 2 + .../dbops/restore_controller_dbops.class.php | 1 + .../ui/tests/behat/backup_xapistate.feature | 103 ++++++++++++++++++ lang/en/backup.php | 4 + 14 files changed, 336 insertions(+), 1 deletion(-) create mode 100644 backup/util/ui/tests/behat/backup_xapistate.feature diff --git a/admin/settings/courses.php b/admin/settings/courses.php index f98fc636723..c77469f4b50 100644 --- a/admin/settings/courses.php +++ b/admin/settings/courses.php @@ -357,6 +357,12 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) { new lang_string('configgeneralcontentbankcontent', 'backup'), ['value' => 1, 'locked' => 0]) ); + $temp->add(new admin_setting_configcheckbox_with_lock('backup/backup_general_xapistate', + new lang_string('generalxapistate', 'backup'), + new lang_string('configgeneralxapistate', 'backup'), + ['value' => 1, 'locked' => 0]) + ); + $temp->add(new admin_setting_configcheckbox_with_lock('backup/backup_general_legacyfiles', new lang_string('generallegacyfiles', 'backup'), @@ -521,6 +527,12 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) { new lang_string('configgeneralcontentbankcontent', 'backup'), 1) ); + $temp->add(new admin_setting_configcheckbox( + 'backup/backup_auto_xapistate', + new lang_string('generalxapistate', 'backup'), + new lang_string('configgeneralxapistate', 'backup'), + 1) + ); $temp->add(new admin_setting_configcheckbox('backup/backup_auto_legacyfiles', new lang_string('generallegacyfiles', 'backup'), @@ -591,6 +603,9 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) { $temp->add(new admin_setting_configcheckbox_with_lock('restore/restore_general_contentbankcontent', new lang_string('generalcontentbankcontent', 'backup'), new lang_string('configrestorecontentbankcontent', 'backup'), array('value' => 1, 'locked' => 0))); + $temp->add(new admin_setting_configcheckbox_with_lock('restore/restore_general_xapistate', + new lang_string('generalxapistate', 'backup'), + new lang_string('configrestorexapistate', 'backup'), array('value' => 1, 'locked' => 0))); $temp->add(new admin_setting_configcheckbox_with_lock('restore/restore_general_legacyfiles', new lang_string('generallegacyfiles', 'backup'), new lang_string('configlegacyfiles', 'backup'), array('value' => 1, 'locked' => 0))); diff --git a/backup/moodle2/backup_activity_task.class.php b/backup/moodle2/backup_activity_task.class.php index f5a0348da00..b0100690552 100644 --- a/backup/moodle2/backup_activity_task.class.php +++ b/backup/moodle2/backup_activity_task.class.php @@ -204,6 +204,11 @@ abstract class backup_activity_task extends backup_task { // Migrate the already exported inforef entries to final ones $this->add_step(new move_inforef_annotations_to_final('migrate_inforef')); + // Generate the xAPI state file (conditionally). + if ($this->get_setting_value('xapistate')) { + $this->add_step(new backup_xapistate_structure_step('activity_xapistate', 'xapistate.xml')); + } + // At the end, mark it as built $this->built = true; } diff --git a/backup/moodle2/backup_root_task.class.php b/backup/moodle2/backup_root_task.class.php index 673b58b9d4e..79524fe60fa 100644 --- a/backup/moodle2/backup_root_task.class.php +++ b/backup/moodle2/backup_root_task.class.php @@ -192,6 +192,12 @@ class backup_root_task extends backup_task { $contentbank->set_ui(new backup_setting_ui_checkbox($contentbank, get_string('rootsettingcontentbankcontent', 'backup'))); $this->add_setting($contentbank); + // Define xAPI state inclusion setting. + $xapistate = new backup_xapistate_setting('xapistate', base_setting::IS_BOOLEAN, true); + $xapistate->set_ui(new backup_setting_ui_checkbox($xapistate, get_string('rootsettingxapistate', 'backup'))); + $this->add_setting($xapistate); + $users->add_dependency($xapistate); + // Define legacy file inclusion setting. $legacyfiles = new backup_generic_setting('legacyfiles', base_setting::IS_BOOLEAN, true); $legacyfiles->set_ui(new backup_setting_ui_checkbox($legacyfiles, get_string('rootsettinglegacyfiles', 'backup'))); diff --git a/backup/moodle2/backup_settingslib.php b/backup/moodle2/backup_settingslib.php index ca9224f1707..6767d01f057 100644 --- a/backup/moodle2/backup_settingslib.php +++ b/backup/moodle2/backup_settingslib.php @@ -211,3 +211,9 @@ class backup_activity_userinfo_setting extends activity_backup_setting {} */ class backup_contentbankcontent_setting extends backup_generic_setting { } + +/** + * Root setting to control if backup will include xAPI state or not. + */ +class backup_xapistate_setting extends backup_generic_setting { +} diff --git a/backup/moodle2/backup_stepslib.php b/backup/moodle2/backup_stepslib.php index a09c1dc0741..ccdd7b58bb0 100644 --- a/backup/moodle2/backup_stepslib.php +++ b/backup/moodle2/backup_stepslib.php @@ -2987,3 +2987,35 @@ class backup_contentbankcontent_structure_step extends backup_structure_step { return $contents; } } + +/** + * Structure step in charge of constructing the xapistate.xml file for all the xAPI states found in a given context. + */ +class backup_xapistate_structure_step extends backup_structure_step { + + /** + * Define structure for content bank step + */ + protected function define_structure() { + + // Define each element separated. + $states = new backup_nested_element('states'); + $state = new backup_nested_element( + 'state', + ['id'], + ['component', 'userid', 'itemid', 'stateid', 'statedata', 'registration', 'timecreated', 'timemodified'] + ); + + // Build the tree. + $states->add_child($state); + + // Define sources. + $state->set_source_table('xapi_states', ['itemid' => backup::VAR_CONTEXTID]); + + // Define annotations. + $state->annotate_ids('user', 'userid'); + + // Return the root element (contents). + return $states; + } +} diff --git a/backup/moodle2/restore_activity_task.class.php b/backup/moodle2/restore_activity_task.class.php index 687a54508e9..90bcef913d7 100644 --- a/backup/moodle2/restore_activity_task.class.php +++ b/backup/moodle2/restore_activity_task.class.php @@ -200,6 +200,11 @@ abstract class restore_activity_task extends restore_task { } } + // The xAPI state (conditionally). + if ($this->get_setting_value('xapistate')) { + $this->add_step(new restore_xapistate_structure_step('activity_xapistate', 'xapistate.xml')); + } + // At the end, mark it as built $this->built = true; } diff --git a/backup/moodle2/restore_root_task.class.php b/backup/moodle2/restore_root_task.class.php index 40345c12aad..107463f1c4f 100644 --- a/backup/moodle2/restore_root_task.class.php +++ b/backup/moodle2/restore_root_task.class.php @@ -316,6 +316,18 @@ class restore_root_task extends restore_task { $contents->get_ui()->set_changeable($changeable); $this->add_setting($contents); + // Define xAPI states. + $defaultvalue = false; + $changeable = false; + if (isset($rootsettings['xapistate']) && $rootsettings['xapistate']) { // Only enabled when available. + $defaultvalue = true; + $changeable = true; + } + $xapistate = new restore_xapistate_setting('xapistate', base_setting::IS_BOOLEAN, $defaultvalue); + $xapistate->set_ui(new backup_setting_ui_checkbox($xapistate, get_string('rootsettingxapistate', 'backup'))); + $xapistate->get_ui()->set_changeable($changeable); + $this->add_setting($xapistate); + // Include legacy files. $defaultvalue = true; $changeable = true; diff --git a/backup/moodle2/restore_settingslib.php b/backup/moodle2/restore_settingslib.php index fb9e064fcd2..cce77a98dec 100644 --- a/backup/moodle2/restore_settingslib.php +++ b/backup/moodle2/restore_settingslib.php @@ -248,3 +248,9 @@ class restore_activity_userinfo_setting extends restore_activity_generic_setting */ class restore_contentbankcontent_setting extends restore_generic_setting { } + +/** + * Root setting to control if restore will create xAPI states or not. + */ +class restore_xapistate_setting extends restore_generic_setting { +} diff --git a/backup/moodle2/restore_stepslib.php b/backup/moodle2/restore_stepslib.php index ad500d034ab..f9ec9adccd0 100644 --- a/backup/moodle2/restore_stepslib.php +++ b/backup/moodle2/restore_stepslib.php @@ -4226,6 +4226,61 @@ class restore_contentbankcontent_structure_step extends restore_structure_step { } } +/** + * This structure steps restores the xAPI states. + */ +class restore_xapistate_structure_step extends restore_structure_step { + + /** + * Define structure for xAPI state step + */ + protected function define_structure() { + return [new restore_path_element('xapistate', '/states/state')]; + } + + /** + * Define data processed for xAPI state. + * + * @param array|stdClass $data + */ + public function process_xapistate($data) { + global $DB; + + $data = (object)$data; + $oldid = $data->id; + $exists = false; + + $params = [ + 'component' => $data->component, + 'itemid' => $this->task->get_contextid(), + // Set stateid to 'restored', to let plugins identify the origin of this state is a backup. + 'stateid' => 'restored', + 'statedata' => $data->statedata, + 'registration' => $data->registration, + 'timecreated' => $data->timecreated, + 'timemodified' => time(), + ]; + + // Trying to map users. Users cannot always be mapped, for instance, when copying. + $params['userid'] = $this->get_mappingid('user', $data->userid); + if (!$params['userid']) { + // Leave the userid unchanged when we are restoring the same site. + if ($this->task->is_samesite()) { + $params['userid'] = $data->userid; + } + $filter = $params; + unset($filter['statedata']); + $exists = $DB->record_exists('xapi_states', $filter); + } + + if (!$exists && $params['userid']) { + // Only insert the record if the user exists or can be mapped. + $newitemid = $DB->insert_record('xapi_states', $params); + $this->set_mapping('xapi_states', $oldid, $newitemid, true); + } + } +} + /** * This structure steps restores one instance + positions of one block * Note: Positions corresponding to one existing context are restored diff --git a/backup/moodle2/tests/moodle2_test.php b/backup/moodle2/tests/moodle2_test.php index 778ef6156a7..4e438edfee7 100644 --- a/backup/moodle2/tests/moodle2_test.php +++ b/backup/moodle2/tests/moodle2_test.php @@ -18,6 +18,7 @@ namespace core_backup; use backup; use backup_controller; +use backup_setting; use restore_controller; use restore_dbops; @@ -463,9 +464,10 @@ class moodle2_test extends \advanced_testcase { * @param \stdClass $course Course object to backup * @param int $newdate If non-zero, specifies custom date for new course * @param callable|null $inbetween If specified, function that is called before restore + * @param bool $userdata Whether the backup/restory must be with user data or not. * @return int ID of newly restored course */ - protected function backup_and_restore($course, $newdate = 0, $inbetween = null) { + protected function backup_and_restore($course, $newdate = 0, $inbetween = null, bool $userdata = false) { global $USER, $CFG; // Turn off file logging, otherwise it can't delete the file (Windows). @@ -476,6 +478,9 @@ class moodle2_test extends \advanced_testcase { $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id); + $bc->get_plan()->get_setting('users')->set_status(backup_setting::NOT_LOCKED); + $bc->get_plan()->get_setting('users')->set_value($userdata); + $backupid = $bc->get_backupid(); $bc->execute_plan(); $bc->destroy(); @@ -493,6 +498,13 @@ class moodle2_test extends \advanced_testcase { if ($newdate) { $rc->get_plan()->get_setting('course_startdate')->set_value($newdate); } + + $rc->get_plan()->get_setting('users')->set_status(backup_setting::NOT_LOCKED); + $rc->get_plan()->get_setting('users')->set_value($userdata); + if ($userdata) { + $rc->get_plan()->get_setting('xapistate')->set_value(true); + } + $this->assertTrue($rc->execute_precheck()); $rc->execute_plan(); $rc->destroy(); @@ -1090,4 +1102,75 @@ class moodle2_test extends \advanced_testcase { $this->assertEquals(4, $DB->count_records('contentbank_content')); $this->assertEquals(2, $DB->count_records('contentbank_content', ['contextid' => $newcontext->id])); } + + /** + * Test the xAPI state through a backup and restore. + * + * @covers \backup_xapistate_structure_step + * @covers \restore_xapistate_structure_step + */ + public function test_xapistate_backup() { + global $DB; + $this->resetAfterTest(); + $this->setAdminUser(); + + $course = $this->getDataGenerator()->create_course(); + $user = $this->getDataGenerator()->create_and_enrol($course, 'student'); + $activity = $this->getDataGenerator()->create_module('h5pactivity', ['course' => $course]); + $this->setUser($user); + + /** @var \mod_h5pactivity_generator $generator */ + $generator = $this->getDataGenerator()->get_plugin_generator('mod_h5pactivity'); + + /** @var \core_h5p_generator $h5pgenerator */ + $h5pgenerator = $this->getDataGenerator()->get_plugin_generator('core_h5p'); + + // Add an attempt to the H5P activity. + $attemptinfo = [ + 'userid' => $user->id, + 'h5pactivityid' => $activity->id, + 'attempt' => 1, + 'interactiontype' => 'compound', + 'rawscore' => 2, + 'maxscore' => 2, + 'duration' => 1, + 'completion' => 1, + 'success' => 0, + ]; + $generator->create_attempt($attemptinfo); + + // Add also a xAPI state to the H5P activity. + $filerecord = [ + 'contextid' => \context_module::instance($activity->cmid)->id, + 'component' => 'mod_h5pactivity', + 'filearea' => 'package', + 'itemid' => 0, + 'filepath' => '/', + 'filepath' => '/', + 'filename' => 'dummy.h5p', + 'addxapistate' => true, + ]; + $h5pgenerator->generate_h5p_data(false, $filerecord); + + // Check the H5P activity exists and the attempt has been created. + $this->assertEquals(1, $DB->count_records('h5pactivity')); + $this->assertEquals(2, $DB->count_records('grade_items')); + $this->assertEquals(2, $DB->count_records('grade_grades')); + $this->assertEquals(1, $DB->count_records('xapi_states')); + + // Do backup and restore. + $this->setAdminUser(); + $newcourseid = $this->backup_and_restore($course, 0, null, true); + + // Confirm that values were transferred correctly into H5P activity on new course. + $this->assertEquals(2, $DB->count_records('h5pactivity')); + $this->assertEquals(4, $DB->count_records('grade_items')); + $this->assertEquals(4, $DB->count_records('grade_grades')); + $this->assertEquals(2, $DB->count_records('xapi_states')); + + $newactivity = $DB->get_record('h5pactivity', ['course' => $newcourseid]); + $cm = get_coursemodule_from_instance('h5pactivity', $newactivity->id); + $context = \context_module::instance($cm->id); + $this->assertEquals(1, $DB->count_records('xapi_states', ['itemid' => $context->id])); + } } diff --git a/backup/util/dbops/backup_controller_dbops.class.php b/backup/util/dbops/backup_controller_dbops.class.php index d6df561a003..2bc367fd476 100644 --- a/backup/util/dbops/backup_controller_dbops.class.php +++ b/backup/util/dbops/backup_controller_dbops.class.php @@ -567,6 +567,7 @@ abstract class backup_controller_dbops extends backup_dbops { 'backup_general_groups' => 'groups', 'backup_general_competencies' => 'competencies', 'backup_general_contentbankcontent' => 'contentbankcontent', + 'backup_general_xapistate' => 'xapistate', 'backup_general_legacyfiles' => 'legacyfiles' ); self::apply_admin_config_defaults($controller, $settings, true); @@ -616,6 +617,7 @@ abstract class backup_controller_dbops extends backup_dbops { 'backup_auto_groups' => 'groups', 'backup_auto_competencies' => 'competencies', 'backup_auto_contentbankcontent' => 'contentbankcontent', + 'backup_auto_xapistate' => 'xapistate', 'backup_auto_legacyfiles' => 'legacyfiles' ); self::apply_admin_config_defaults($controller, $settings, false); diff --git a/backup/util/dbops/restore_controller_dbops.class.php b/backup/util/dbops/restore_controller_dbops.class.php index 4cc7ee21851..91849434485 100644 --- a/backup/util/dbops/restore_controller_dbops.class.php +++ b/backup/util/dbops/restore_controller_dbops.class.php @@ -160,6 +160,7 @@ abstract class restore_controller_dbops extends restore_dbops { 'restore_general_groups' => 'groups', 'restore_general_competencies' => 'competencies', 'restore_general_contentbankcontent' => 'contentbankcontent', + 'restore_general_xapistate' => 'xapistate', 'restore_general_legacyfiles' => 'legacyfiles' ); self::apply_admin_config_defaults($controller, $settings, true); diff --git a/backup/util/ui/tests/behat/backup_xapistate.feature b/backup/util/ui/tests/behat/backup_xapistate.feature new file mode 100644 index 00000000000..e0c14348f42 --- /dev/null +++ b/backup/util/ui/tests/behat/backup_xapistate.feature @@ -0,0 +1,103 @@ +@core @core_backup @core_h5p @mod_h5pactivity @_switch_iframe @javascript +Feature: Backup xAPI states + In order to save and restore xAPI states + As an admin + I need to create backups with xAPI states and restore them + + Background: + Given the following "users" exist: + | username | firstname | lastname | email | + | student1 | Student | 1 | student1@example.com | + And the following "course" exists: + | fullname | Course 1 | + | shortname | C1 | + And the following "course enrolments" exist: + | user | course | role | + | student1 | C1 | student | + And the following "activity" exists: + | activity | h5pactivity | + | course | C1 | + | name | Awesome H5P package | + | packagefilepath | h5p/tests/fixtures/filltheblanks.h5p | + # Save state for the student user. + And I am on the "Awesome H5P package" "h5pactivity activity" page logged in as student1 + And I switch to "h5p-player" class iframe + And I switch to "h5p-iframe" class iframe + And I set the field with xpath "//input[contains(@aria-label,\"Blank input 1 of 4\")]" to "Narnia" + And I switch to the main frame + And I am on the "Course 1" course page + And I am on the "Awesome H5P package" "h5pactivity activity" page + And I switch to "h5p-player" class iframe + And I switch to "h5p-iframe" class iframe + And the field with xpath "//input[contains(@aria-label,\"Blank input 1 of 4\")]" matches value "Narnia" + And I log out + + Scenario: Content state is backup/restored when user data is included + # Backup and restore the course. + Given I log in as "admin" + And I backup "Course 1" course using this options: + | Confirmation | Filename | test_backup.mbz | + And I restore "test_backup.mbz" backup into a new course using this options: + | Settings | Include enrolled users | 1 | + | Schema | User data | 1 | + | Schema | Course name | Course 2 | + | Schema | Course short name | C2 | + # Login as student and confirm xAPI state has been restored. + When I am on the "Course 2" course page logged in as student1 + And I click on "Awesome H5P package" "link" in the "region-main" "region" + And I switch to "h5p-player" class iframe + And I switch to "h5p-iframe" class iframe + Then the field with xpath "//input[contains(@aria-label,\"Blank input 1 of 4\")]" matches value "Narnia" + + Scenario: Content state is not restored when user data is not included in the backup + # Backup course without user data and then restore it. + When I log in as "admin" + And I backup "Course 1" course using this options: + | Initial | Include enrolled users | 0 | + | Confirmation | Filename | test_backup.mbz | + And I restore "test_backup.mbz" backup into a new course using this options: + | Schema | Course name | Course 2 | + | Schema | Course short name | C2 | + # Enrol student to the new course. + And the following "course enrolments" exist: + | user | course | role | + | student1 | C2 | student | + # Login as student and confirm xAPI state hasn't been restored. + And I am on the "Course 2" course page logged in as student1 + And I click on "Awesome H5P package" "link" in the "region-main" "region" + And I switch to "h5p-player" class iframe + And I switch to "h5p-iframe" class iframe + Then the field with xpath "//input[contains(@aria-label,\"Blank input 1 of 4\")]" does not match value "Narnia" + + Scenario: Content state is not restored when user data is included in the backup but xAPI state is not restored + # Backup with user data and restore it without user data the course. + Given I log in as "admin" + And I backup "Course 1" course using this options: + | Confirmation | Filename | test_backup.mbz | + And I restore "test_backup.mbz" backup into a new course using this options: + | Settings | Include user's state in content such as H5P activities | 0 | + | Schema | Course name | Course 2 | + | Schema | Course short name | C2 | + # Login as student and confirm xAPI state hasn't been restored. + When I am on the "Course 2" course page logged in as student1 + And I click on "Awesome H5P package" "link" in the "region-main" "region" + And I switch to "h5p-player" class iframe + And I switch to "h5p-iframe" class iframe + Then the field with xpath "//input[contains(@aria-label,\"Blank input 1 of 4\")]" does not match value "Narnia" + + Scenario: Content state is not restored when it is not included explicitly in the backup + # Backup course with user data but without xAPI state and then restore it. + When I log in as "admin" + And I backup "Course 1" course using this options: + | Initial | Include user's state in content such as H5P activities | 0 | + | Confirmation | Filename | test_backup.mbz | + And I restore "test_backup.mbz" backup into a new course using this options: + | Schema | Course name | Course 2 | + | Schema | Course short name | C2 | + And I should see "Awesome H5P package" + # Login as student and confirm xAPI state hasn't been restored. + And I am on the "Course 2" course page logged in as student1 + And I click on "Awesome H5P package" "link" in the "region-main" "region" + And I switch to "h5p-player" class iframe + And I switch to "h5p-iframe" class iframe + Then the field with xpath "//input[contains(@aria-label,\"Blank input 1 of 4\")]" does not match value "Narnia" diff --git a/lang/en/backup.php b/lang/en/backup.php index 6e6b5c3dea4..1a243388658 100644 --- a/lang/en/backup.php +++ b/lang/en/backup.php @@ -139,6 +139,7 @@ $string['configgeneralroleassignments'] = 'If enabled by default roles assignmen $string['configgeneralpermissions'] = 'If enabled the role permissions will be imported. This may override existing permissions for enrolled users.'; $string['configgeneraluserscompletion'] = 'If enabled user completion information will be included in backups by default.'; $string['configgeneralusers'] = 'Sets the default for whether to include users in backups.'; +$string['configgeneralxapistate'] = 'Sets the default for including the user\'s state in content such as H5P activities in a backup.'; $string['configlegacyfiles'] = 'Sets the default for including legacy course files in a backup. Legacy course files are from versions of Moodle prior to 2.0.'; $string['configloglifetime'] = 'This specifies the length of time you want to keep backup logs information. Logs that are older than this age are automatically deleted. It is recommended to keep this value small, because backup logged information can be huge.'; $string['configrestoreactivities'] = 'Sets the default for restoring activities.'; @@ -157,6 +158,7 @@ $string['configrestoreroleassignments'] = 'If enabled by default roles assignmen $string['configrestorepermissions'] = 'If enabled the role permissions will be restored. This may override existing permissions for enrolled users.'; $string['configrestoreuserscompletion'] = 'If enabled user completion information will be restored by default if it was included in the backup.'; $string['configrestoreusers'] = 'Sets the default for whether to restore users if they were included in the backup.'; +$string['configrestorexapistate'] = 'Sets the default for restoring the user\'s state in content such as H5P activities.'; $string['confirmcancel'] = 'Cancel backup'; $string['confirmcancelrestore'] = 'Cancel restore'; $string['confirmcancelimport'] = 'Cancel import'; @@ -244,6 +246,7 @@ $string['generalpermissions'] = 'Include permission overrides'; $string['generalsettings'] = 'General backup settings'; $string['generaluserscompletion'] = 'Include user completion information'; $string['generalusers'] = 'Include users'; +$string['generalxapistate'] = 'Include user\'s state in content such as H5P activities'; $string['hidetypes'] = 'Hide type options'; $string['importgeneralsettings'] = 'General import defaults'; $string['importgeneralmaxresults'] = 'Maximum number of courses listed for import'; @@ -372,6 +375,7 @@ $string['rootsettinggradehistories'] = 'Include grade history'; $string['rootsettinggroups'] = 'Include groups and groupings'; $string['rootsettingimscc1'] = 'Convert to IMS Common Cartridge 1.0'; $string['rootsettingimscc11'] = 'Convert to IMS Common Cartridge 1.1'; +$string['rootsettingxapistate'] = 'Include user\'s state in content such as H5P activities'; $string['samesitenotification'] = 'This backup was created with only references to files, not the files themselves. Restoring will only work on this site.'; $string['sitecourseformatwarning'] = 'This is a site home backup. It can only be restored on the site home.'; $string['storagecourseonly'] = 'Course backup filearea';