From 07cb9ff078613a8b09ad5413d1bcf5f6bcdcc40e Mon Sep 17 00:00:00 2001 From: Gregory Faller Date: Wed, 13 May 2015 13:19:10 +0930 Subject: [PATCH 1/4] MDL-34161 mod_lti: Add support for restoring site tool --- .../backup/moodle2/backup_lti_stepslib.php | 42 +++++++++-- .../backup/moodle2/restore_lti_stepslib.php | 72 +++++++++++++++++-- 2 files changed, 106 insertions(+), 8 deletions(-) diff --git a/mod/lti/backup/moodle2/backup_lti_stepslib.php b/mod/lti/backup/moodle2/backup_lti_stepslib.php index e6ad077ef22..0dbc7782f70 100644 --- a/mod/lti/backup/moodle2/backup_lti_stepslib.php +++ b/mod/lti/backup/moodle2/backup_lti_stepslib.php @@ -55,8 +55,6 @@ class backup_lti_activity_structure_step extends backup_activity_structure_step protected function define_structure() { - // TODO: MDL-34161 - Fix restore to support course/site tools & submissions. - // To know if we are including userinfo. $userinfo = $this->get_setting_value('userinfo'); @@ -89,14 +87,50 @@ class backup_lti_activity_structure_step extends backup_activity_structure_step ) ); + $ltitypes = new backup_nested_element('ltitypes'); + $ltitype = new backup_nested_element('ltitype', array('id'), array( + 'name', + 'baseurl', + 'tooldomain', + 'state', + 'coursevisible', + 'createdby', + 'timecreated', + 'timemodified', + ) + ); + + $ltitypesconfigs = new backup_nested_element('ltitypesconfigs'); + $ltitypesconfig = new backup_nested_element('ltitypesconfig', array('id'), array( + 'typeid', + 'name', + 'value', + ) + ); + // Build the tree - // (none). + $lti->add_child($ltitypes); + $lti->add_child($ltitypesconfigs); + $ltitypes->add_child($ltitype); + $ltitypesconfigs->add_child($ltitypesconfig); // Define sources. $lti->set_source_table('lti', array('id' => backup::VAR_ACTIVITYID)); + $ltitype->set_source_sql("SELECT lt.* + FROM {lti} l + JOIN {lti_types} lt ON lt.id = l.typeid + WHERE l.id = ?", array(backup::VAR_ACTIVITYID)); + $ltitypesconfig->set_source_sql("SELECT lc.* + FROM {lti} l + JOIN {lti_types_config} lc ON lc.typeid = l.typeid + WHERE lc.name != 'password' + AND lc.name != 'resourcekey' + AND lc.name != 'servicesalt' + AND l.id = ?", array(backup::VAR_ACTIVITYID)); + // Define id annotations - // (none). + $ltitype->annotate_ids('user', 'createdby'); // Define file annotations. $lti->annotate_files('mod_lti', 'intro', null); // This file areas haven't itemid. diff --git a/mod/lti/backup/moodle2/restore_lti_stepslib.php b/mod/lti/backup/moodle2/restore_lti_stepslib.php index 916c0dcfafd..56da8a52367 100644 --- a/mod/lti/backup/moodle2/restore_lti_stepslib.php +++ b/mod/lti/backup/moodle2/restore_lti_stepslib.php @@ -57,6 +57,8 @@ class restore_lti_activity_structure_step extends restore_activity_structure_ste $paths = array(); $lti = new restore_path_element('lti', '/activity/lti'); + $paths[] = new restore_path_element('ltitype', '/activity/lti/ltitypes/ltitype'); + $paths[] = new restore_path_element('ltitypesconfig', '/activity/lti/ltitypesconfigs/ltitypesconfig'); $paths[] = $lti; // Add support for subplugin structures. @@ -78,10 +80,6 @@ class restore_lti_activity_structure_step extends restore_activity_structure_ste // Grade used to be a float (whole numbers only), restore as int. $data->grade = (int) $data->grade; - // Clean any course or site typeid. All modules - // are restored as self-contained. Note this is - // an interim solution until the issue below is implemented. - // TODO: MDL-34161 - Fix restore to support course/site tools & submissions. $data->typeid = 0; // Try to decrypt resourcekey and password. Null if not possible (DB default). @@ -95,6 +93,72 @@ class restore_lti_activity_structure_step extends restore_activity_structure_ste $this->apply_activity_instance($newitemid); } + /** + * Process an lti type restore + * @param object $data The data in object form + * @return void + */ + protected function process_ltitype($data) { + global $DB; + + $data = (object)$data; + $data->createdby = $this->get_mappingid('user', $data->createdby); + $ltitype = $DB->get_record_sql("SELECT * + FROM {lti_types} + WHERE id = ? + AND baseurl = ?", array($data->id, $data->baseurl)); + + // If restore is occurring on the same site, don't add lti_types data if + // restoring on the SITEID. If restore isn't occurring on the same site, + // always add lti_type data from backup. + if ($this->task->is_samesite() && $ltitype->course != SITEID + && $ltitype->state == LTI_TOOL_STATE_CONFIGURED) { + // If restoring into the same course, use existing data, else re-create. + $course = $this->get_courseid(); + if ($ltitype->course != $course) { + $data->course = $course; + $ltitype = new stdClass(); + $ltitype->id = $DB->insert_record('lti_types', $data); + } + } else if (!$this->task->is_samesite() || !isset($ltitype->id)) { + // Either we are restoring into a new site, or didn't find a database match. + $data->course = $this->get_courseid(); + $ltitype = new stdClass(); + $ltitype->id = $DB->insert_record('lti_types', $data); + } + + // Add the typeid entry back to LTI module. + $lti = new stdClass(); + $lti->id = $this->get_new_parentid('lti'); + $lti->typeid = $ltitype->id; + $DB->update_record('lti', $lti); + } + + /** + * Process an lti config restore + * @param object $data The data in object form + * @return void + */ + protected function process_ltitypesconfig($data) { + global $DB; + + $data = (object)$data; + + $parentid = $this->get_new_parentid('lti'); + $lti = $DB->get_record_sql("SELECT typeid + FROM {lti} + WHERE id = ?", array($parentid)); + + // Only add configuration if typeid doesn't match new LTI tool. + if ($lti->typeid != $data->typeid) { + $data->typeid = $lti->typeid; + if ($data->name == 'servicesalt') { + $data->value = uniqid('', true); + } + $DB->insert_record('lti_types_config', $data); + } + } + protected function after_execute() { // Add lti related files, no need to match by itemname (just internally handled context). $this->add_related_files('mod_lti', 'intro', null); From 4826b9d4543e0671a1a513d44135fbd1466f563b Mon Sep 17 00:00:00 2001 From: Tim Lock Date: Wed, 13 May 2015 13:19:18 +0930 Subject: [PATCH 2/4] MDL-34161 mod_lti: Added Behat tests --- .../backup/moodle2/restore_lti_stepslib.php | 8 +++-- mod/lti/tests/behat/config_test.feature | 35 +++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 mod/lti/tests/behat/config_test.feature diff --git a/mod/lti/backup/moodle2/restore_lti_stepslib.php b/mod/lti/backup/moodle2/restore_lti_stepslib.php index 56da8a52367..ce10bfc23f9 100644 --- a/mod/lti/backup/moodle2/restore_lti_stepslib.php +++ b/mod/lti/backup/moodle2/restore_lti_stepslib.php @@ -114,14 +114,16 @@ class restore_lti_activity_structure_step extends restore_activity_structure_ste if ($this->task->is_samesite() && $ltitype->course != SITEID && $ltitype->state == LTI_TOOL_STATE_CONFIGURED) { // If restoring into the same course, use existing data, else re-create. - $course = $this->get_courseid(); - if ($ltitype->course != $course) { - $data->course = $course; + $courseid = $this->get_courseid(); + if ($ltitype->course != $courseid) { + // Override course field of restore data with current courseid. + $data->course = $courseid; $ltitype = new stdClass(); $ltitype->id = $DB->insert_record('lti_types', $data); } } else if (!$this->task->is_samesite() || !isset($ltitype->id)) { // Either we are restoring into a new site, or didn't find a database match. + // Override course field of restore data with current courseid. $data->course = $this->get_courseid(); $ltitype = new stdClass(); $ltitype->id = $DB->insert_record('lti_types', $data); diff --git a/mod/lti/tests/behat/config_test.feature b/mod/lti/tests/behat/config_test.feature new file mode 100644 index 00000000000..32c6245b130 --- /dev/null +++ b/mod/lti/tests/behat/config_test.feature @@ -0,0 +1,35 @@ +@mod @mod_lti @core_backup @javascript +Feature: Restoring Moodle 2 backup restores LTI configuration + + Background: + Given the following "courses" exist: + | fullname | shortname | category | idnumber | + | Course 1 | C1 | 0 | C1 | + And I log in as "admin" + + Scenario: Backup and restore course 1 + Given I navigate to "Manage external tool types" node in "Site administration > Plugins > Activity modules > LTI" + And I follow "Add external tool configuration" + And I set the following fields to these values: + | Tool name | Site Netspot Tool | + | Tool base URL | http://www.netspot.com.au | + | lti_coursevisible | 1 | + And I press "Save changes" + And I wait to be redirected + Given I am on site homepage + Then I follow "Course 1" + Then I turn editing mode on + And I add a "External tool" to section "1" and I fill the form with: + | Activity name | Site Netspot Tool | + | External tool type | Site Netspot Tool | + | Launch container | Embed | + And I follow "Course 1" + Then I should see "Site Netspot Tool" + Then 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: + Then I am on site homepage + And I follow "Course 1 copy 1" + And I open "Site Netspot Tool" actions menu + And I click on "Edit settings" "link" in the "Site Netspot Tool" activity + Then the field "External tool type" matches value "Site Netspot Tool" \ No newline at end of file From ac1f4d963324c05d9f6e489f1d2ca22b52615718 Mon Sep 17 00:00:00 2001 From: Marina Glancy Date: Fri, 29 Apr 2016 20:31:04 +0800 Subject: [PATCH 3/4] MDL-34161 mod_lti: backup/restore of lti types Only course tools are backed up, site tools and registrations can be matched by id if they are restored to the same site only. For predefined course tools the secret is backed up encrypted and can be restored on the same site only. --- .../backup/moodle2/backup_lti_stepslib.php | 115 +++++++++--- .../backup/moodle2/restore_lti_stepslib.php | 166 +++++++++++++----- mod/lti/tests/behat/addtype.feature | 4 + mod/lti/tests/behat/backup_restore.feature | 84 +++++++++ mod/lti/tests/behat/config_test.feature | 35 ---- 5 files changed, 307 insertions(+), 97 deletions(-) create mode 100644 mod/lti/tests/behat/backup_restore.feature delete mode 100644 mod/lti/tests/behat/config_test.feature diff --git a/mod/lti/backup/moodle2/backup_lti_stepslib.php b/mod/lti/backup/moodle2/backup_lti_stepslib.php index 0dbc7782f70..34893c1d82e 100644 --- a/mod/lti/backup/moodle2/backup_lti_stepslib.php +++ b/mod/lti/backup/moodle2/backup_lti_stepslib.php @@ -53,7 +53,12 @@ defined('MOODLE_INTERNAL') || die; */ class backup_lti_activity_structure_step extends backup_activity_structure_step { + /** + * Defines structure of activity backup + * @return backup_nested_element + */ protected function define_structure() { + global $DB; // To know if we are including userinfo. $userinfo = $this->get_setting_value('userinfo'); @@ -87,50 +92,90 @@ class backup_lti_activity_structure_step extends backup_activity_structure_step ) ); - $ltitypes = new backup_nested_element('ltitypes'); - $ltitype = new backup_nested_element('ltitype', array('id'), array( + $ltitype = new backup_nested_element('ltitype', array('id'), array( 'name', 'baseurl', 'tooldomain', 'state', + 'course', 'coursevisible', + 'toolproxyid', + 'enabledcapability', + 'parameter', + 'icon', + 'secureicon', 'createdby', 'timecreated', 'timemodified', + 'description' ) ); $ltitypesconfigs = new backup_nested_element('ltitypesconfigs'); $ltitypesconfig = new backup_nested_element('ltitypesconfig', array('id'), array( - 'typeid', - 'name', - 'value', + 'name', + 'value', + ) + ); + $ltitypesconfigencrypted = new backup_nested_element('ltitypesconfigencrypted', array('id'), array( + 'name', + new encrypted_final_element('value'), + ) + ); + + $ltitoolproxy = new backup_nested_element('ltitoolproxy', array('id')); + + $ltitoolsettings = new backup_nested_element('ltitoolsettings'); + $ltitoolsetting = new backup_nested_element('ltitoolsetting', array('id'), array( + 'settings', + 'timecreated', + 'timemodified', ) ); // Build the tree - $lti->add_child($ltitypes); - $lti->add_child($ltitypesconfigs); - $ltitypes->add_child($ltitype); + $lti->add_child($ltitype); + $ltitype->add_child($ltitypesconfigs); $ltitypesconfigs->add_child($ltitypesconfig); + $ltitypesconfigs->add_child($ltitypesconfigencrypted); + $ltitype->add_child($ltitoolproxy); + $ltitoolproxy->add_child($ltitoolsettings); + $ltitoolsettings->add_child($ltitoolsetting); // Define sources. - $lti->set_source_table('lti', array('id' => backup::VAR_ACTIVITYID)); + $ltirecord = $DB->get_record('lti', ['id' => $this->task->get_activityid()]); + $lti->set_source_array([$ltirecord]); - $ltitype->set_source_sql("SELECT lt.* - FROM {lti} l - JOIN {lti_types} lt ON lt.id = l.typeid - WHERE l.id = ?", array(backup::VAR_ACTIVITYID)); - $ltitypesconfig->set_source_sql("SELECT lc.* - FROM {lti} l - JOIN {lti_types_config} lc ON lc.typeid = l.typeid - WHERE lc.name != 'password' - AND lc.name != 'resourcekey' - AND lc.name != 'servicesalt' - AND l.id = ?", array(backup::VAR_ACTIVITYID)); + $ltitypedata = $this->retrieve_lti_type($ltirecord); + $ltitype->set_source_array($ltitypedata ? [$ltitypedata] : []); + + if (isset($ltitypedata->baseurl)) { + // Add type config values only if the type was backed up. Encrypt password and resourcekey. + $params = [backup_helper::is_sqlparam($ltitypedata->id), + backup_helper::is_sqlparam('password'), + backup_helper::is_sqlparam('resourcekey')]; + $ltitypesconfig->set_source_sql("SELECT id, name, value + FROM {lti_types_config} + WHERE typeid = ? AND name <> ? AND name <> ?", $params); + $ltitypesconfigencrypted->set_source_sql("SELECT id, name, value + FROM {lti_types_config} + WHERE typeid = ? AND (name = ? OR name = ?)", $params); + } + + if (!empty($ltitypedata->toolproxyid)) { + // If this is LTI 2 tool add settings for the current activity. + $ltitoolproxy->set_source_array([['id' => $ltitypedata->toolproxyid]]); + $ltitoolsetting->set_source_sql("SELECT * + FROM {lti_tool_settings} + WHERE toolproxyid = ? AND course = ? AND coursemoduleid = ?", + [backup_helper::is_sqlparam($ltitypedata->toolproxyid), backup::VAR_COURSEID, backup::VAR_MODID]); + } else { + $ltitoolproxy->set_source_array([]); + } // Define id annotations $ltitype->annotate_ids('user', 'createdby'); + $ltitype->annotate_ids('course', 'course'); // Define file annotations. $lti->annotate_files('mod_lti', 'intro', null); // This file areas haven't itemid. @@ -142,4 +187,34 @@ class backup_lti_activity_structure_step extends backup_activity_structure_step // Return the root element (lti), wrapped into standard activity structure. return $this->prepare_activity_structure($lti); } + + /** + * Retrieves a record from {lti_type} table associated with the current activity + * + * Information about site tools is not returned because it is insecure to back it up, + * only fields necessary for same-site tool matching are left in the record + * + * @param stdClass $ltirecord record from {lti} table + * @return stdClass|null + */ + protected function retrieve_lti_type($ltirecord) { + global $DB; + if (!$ltirecord->typeid) { + return null; + } + + $record = $DB->get_record('lti_types', ['id' => $ltirecord->typeid]); + if ($record && $record->course == SITEID) { + // Site LTI types or registrations are not backed up except for their name (which is visible). + // Predefined course types can be backed up. + $allowedkeys = ['id', 'course', 'name', 'toolproxyid']; + foreach ($record as $key => $value) { + if (!in_array($key, $allowedkeys)) { + $record->$key = null; + } + } + } + + return $record; + } } diff --git a/mod/lti/backup/moodle2/restore_lti_stepslib.php b/mod/lti/backup/moodle2/restore_lti_stepslib.php index ce10bfc23f9..7a542e7333c 100644 --- a/mod/lti/backup/moodle2/restore_lti_stepslib.php +++ b/mod/lti/backup/moodle2/restore_lti_stepslib.php @@ -53,13 +53,20 @@ defined('MOODLE_INTERNAL') || die; */ class restore_lti_activity_structure_step extends restore_activity_structure_step { + /** @var bool */ + protected $newltitype = false; + protected function define_structure() { $paths = array(); $lti = new restore_path_element('lti', '/activity/lti'); - $paths[] = new restore_path_element('ltitype', '/activity/lti/ltitypes/ltitype'); - $paths[] = new restore_path_element('ltitypesconfig', '/activity/lti/ltitypesconfigs/ltitypesconfig'); $paths[] = $lti; + $paths[] = new restore_path_element('ltitype', '/activity/lti/ltitype'); + $paths[] = new restore_path_element('ltitypesconfig', '/activity/lti/ltitype/ltitypesconfigs/ltitypesconfig'); + $paths[] = new restore_path_element('ltitypesconfigencrypted', + '/activity/lti/ltitype/ltitypesconfigs/ltitypesconfigencrypted'); + $paths[] = new restore_path_element('ltitoolproxy', '/activity/lti/ltitype/ltitoolproxy'); + $paths[] = new restore_path_element('ltitoolsetting', '/activity/lti/ltitype/ltitoolproxy/ltitoolsettings/ltitoolsetting'); // Add support for subplugin structures. $this->add_subplugin_structure('ltisource', $lti); @@ -95,65 +102,93 @@ class restore_lti_activity_structure_step extends restore_activity_structure_ste /** * Process an lti type restore - * @param object $data The data in object form + * @param mixed $data The data from backup XML file * @return void */ protected function process_ltitype($data) { - global $DB; + global $DB, $USER; $data = (object)$data; - $data->createdby = $this->get_mappingid('user', $data->createdby); - $ltitype = $DB->get_record_sql("SELECT * - FROM {lti_types} - WHERE id = ? - AND baseurl = ?", array($data->id, $data->baseurl)); + $oldid = $data->id; + if (!empty($data->createdby)) { + $data->createdby = $this->get_mappingid('user', $data->createdby) ?: $USER->id; + } - // If restore is occurring on the same site, don't add lti_types data if - // restoring on the SITEID. If restore isn't occurring on the same site, - // always add lti_type data from backup. - if ($this->task->is_samesite() && $ltitype->course != SITEID - && $ltitype->state == LTI_TOOL_STATE_CONFIGURED) { - // If restoring into the same course, use existing data, else re-create. - $courseid = $this->get_courseid(); - if ($ltitype->course != $courseid) { - // Override course field of restore data with current courseid. - $data->course = $courseid; - $ltitype = new stdClass(); - $ltitype->id = $DB->insert_record('lti_types', $data); - } - } else if (!$this->task->is_samesite() || !isset($ltitype->id)) { - // Either we are restoring into a new site, or didn't find a database match. - // Override course field of restore data with current courseid. - $data->course = $this->get_courseid(); - $ltitype = new stdClass(); - $ltitype->id = $DB->insert_record('lti_types', $data); + $courseid = $this->get_courseid(); + $data->course = ($this->get_mappingid('course', $data->course) == $courseid) ? $courseid : SITEID; + + // Try to find existing lti type with the same properties. + $ltitypeid = $this->find_existing_lti_type($data); + + $this->newltitype = false; + if (!$ltitypeid && $data->course == $courseid) { + unset($data->toolproxyid); // Course tools can not use LTI2. + $ltitypeid = $DB->insert_record('lti_types', $data); + $this->newltitype = true; + $this->set_mapping('ltitype', $oldid, $ltitypeid); } // Add the typeid entry back to LTI module. - $lti = new stdClass(); - $lti->id = $this->get_new_parentid('lti'); - $lti->typeid = $ltitype->id; - $DB->update_record('lti', $lti); + $DB->update_record('lti', ['id' => $this->get_new_parentid('lti'), 'typeid' => $ltitypeid]); + } + + /** + * Attempts to find existing record in lti_type + * @param stdClass $data + * @return int|null field lti_types.id or null if tool is not found + */ + protected function find_existing_lti_type($data) { + global $DB; + if ($ltitypeid = $this->get_mappingid('ltitype', $data->id)) { + return $ltitypeid; + } + + $ltitype = null; + $params = (array)$data; + if ($this->task->is_samesite()) { + // If we are restoring on the same site try to find lti type with the same id. + $sql = 'id = :id AND course = :course'; + $sql .= ($data->toolproxyid) ? ' AND toolproxyid = :toolproxyid' : ' AND toolproxyid IS NULL'; + if ($DB->record_exists_select('lti_types', $sql, $params)) { + $this->set_mapping('ltitype', $data->id, $data->id); + if ($data->toolproxyid) { + $this->set_mapping('ltitoolproxy', $data->toolproxyid, $data->toolproxyid); + } + return $data->id; + } + } + + if ($data->course != $this->get_courseid()) { + // Site tools are not backed up and are not restored. + return null; + } + + // Now try to find the same type on the current site available in this course. + // Compare only fields baseurl, course and name, if they are the same we assume it is the same tool. + // LTI2 is not possible in the course so we add "lt.toolproxyid IS NULL" to the query. + $sql = 'SELECT id + FROM {lti_types} + WHERE baseurl = :baseurl AND course = :course AND name = :name AND toolproxyid IS NULL'; + if ($ltitype = $DB->get_record_sql($sql, $params, IGNORE_MULTIPLE)) { + $this->set_mapping('ltitype', $data->id, $ltitype->id); + return $ltitype->id; + } + + return null; } /** * Process an lti config restore - * @param object $data The data in object form - * @return void + * @param mixed $data The data from backup XML file */ protected function process_ltitypesconfig($data) { global $DB; $data = (object)$data; + $data->typeid = $this->get_new_parentid('ltitype'); - $parentid = $this->get_new_parentid('lti'); - $lti = $DB->get_record_sql("SELECT typeid - FROM {lti} - WHERE id = ?", array($parentid)); - - // Only add configuration if typeid doesn't match new LTI tool. - if ($lti->typeid != $data->typeid) { - $data->typeid = $lti->typeid; + // Only add configuration if the new lti_type was created. + if ($data->typeid && $this->newltitype) { if ($data->name == 'servicesalt') { $data->value = uniqid('', true); } @@ -161,6 +196,53 @@ class restore_lti_activity_structure_step extends restore_activity_structure_ste } } + /** + * Process an lti config restore + * @param mixed $data The data from backup XML file + */ + protected function process_ltitypesconfigencrypted($data) { + global $DB; + + $data = (object)$data; + $data->typeid = $this->get_new_parentid('ltitype'); + + // Only add configuration if the new lti_type was created. + if ($data->typeid && $this->newltitype) { + $data->value = $this->decrypt($data->value); + if (!is_null($data->value)) { + $DB->insert_record('lti_types_config', $data); + } + } + } + + /** + * Process a restore of LTI tool registration + * This method is empty because we actually process registration as part of process_ltitype() + * @param mixed $data The data from backup XML file + */ + protected function process_ltitoolproxy($data) { + + } + + /** + * Process an lti tool registration settings restore (only settings for the current activity) + * @param mixed $data The data from backup XML file + */ + protected function process_ltitoolsetting($data) { + global $DB; + + $data = (object)$data; + $data->toolproxyid = $this->get_new_parentid('ltitoolproxy'); + + if (!$data->toolproxyid) { + return; + } + + $data->course = $this->get_courseid(); + $data->coursemoduleid = $this->task->get_moduleid(); + $DB->insert_record('lti_tool_settings', $data); + } + protected function after_execute() { // Add lti related files, no need to match by itemname (just internally handled context). $this->add_related_files('mod_lti', 'intro', null); diff --git a/mod/lti/tests/behat/addtype.feature b/mod/lti/tests/behat/addtype.feature index 2381230c8a0..aefff91e7c3 100644 --- a/mod/lti/tests/behat/addtype.feature +++ b/mod/lti/tests/behat/addtype.feature @@ -55,6 +55,10 @@ Feature: Add preconfigured tools via teacher interface And the field "Secure icon URL" matches value "https://download.moodle.org/unittest/test.jpg" And I press "Cancel" And I switch to the main window + And I press "Save and return to course" + And I open "Test tool activity 1" actions menu + And I choose "Edit settings" in the open action menu + And the field "Preconfigured tool" matches value "Placeholder" @javascript @_switch_window Scenario: Add and use a preconfigured tool diff --git a/mod/lti/tests/behat/backup_restore.feature b/mod/lti/tests/behat/backup_restore.feature new file mode 100644 index 00000000000..dea1105ec12 --- /dev/null +++ b/mod/lti/tests/behat/backup_restore.feature @@ -0,0 +1,84 @@ +@mod @mod_lti @core_backup @javascript +Feature: Restoring Moodle 2 backup restores LTI configuration + + Background: + Given the following "users" exist: + | username | firstname | lastname | email | + | teacher1 | Terry1 | Teacher1 | teacher1@example.com | + And the following "courses" exist: + | fullname | shortname | category | + | Course 1 | C1 | 0 | + | Course 2 | C2 | 0 | + And the following "course enrolments" exist: + | user | course | role | + | teacher1 | C1 | editingteacher | + | teacher1 | C2 | editingteacher | + + Scenario: Backup and restore course with preconfigured site LTI tool on the same site + When I log in as "admin" + And I navigate to "Manage tools" node in "Site administration > Plugins > Activity modules > External tool" + And I follow "Manage preconfigured tools" + And I follow "Add preconfigured tool" + And I set the following fields to these values: + | Tool name | My site tool | + | Tool URL | https://www.moodle.org | + | lti_coursevisible | 1 | + And I press "Save changes" + And I navigate to "Manage tools" node in "Site administration > Plugins > Activity modules > External tool" + And "This tool has not yet been used" "text" should exist in the "//div[contains(@id,'tool-card-container') and contains(., 'My site tool')]" "xpath_element" + And I am on site homepage + And I follow "Course 1" + And I turn editing mode on + And I add a "External tool" to section "1" and I fill the form with: + | Activity name | My LTI module | + | Preconfigured tool | My site tool | + | Launch container | Embed | + And I follow "Course 1" + And I should see "My LTI module" + 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: + And I am on site homepage + And I follow "Course 1 copy 1" + And I open "My LTI module" actions menu + And I choose "Edit settings" in the open action menu + Then the field "Preconfigured tool" matches value "My site tool" + And I navigate to "Manage tools" node in "Site administration > Plugins > Activity modules > External tool" + And "This tool is being used 2 times" "text" should exist in the "//div[contains(@id,'tool-card-container') and contains(., 'My site tool')]" "xpath_element" + + @javascript @_switch_window + Scenario: Backup and restore course with preconfigured course LTI tool on the same site + When I log in as "teacher1" + And I am on "Course 1" course homepage with editing mode on + # In the first course create an LTI module that uses a course preconfigured toolю + And I add a "External tool" to section "1" + And I set the following fields to these values: + | Activity name | Test tool activity 2 | + And I follow "Add preconfigured tool" + And I switch to "add_tool" window + And I set the field "Tool name" to "My course tool" + And I set the field "Tool URL" to "http://www.example.com/lti/provider.php" + And I set the field "Consumer key" to "my key" + And I set the field "Shared secret" to "my secret" + And I set the field "Default launch container" to "Existing window" + And I press "Save changes" + And I switch to the main window + And I press "Save and return to course" + # Backup course and restore into another course + And I backup "Course 1" course using this options: + | Confirmation | Filename | test_backup.mbz | + And I restore "test_backup.mbz" backup into "Course 2" course using this options: + And I am on site homepage + And I follow "Course 2" + # Make sure the copy of the preconfigured tool was created in the second course with both encrtypted and non-encrypted properties. + And I open "Test tool activity 2" actions menu + And I choose "Edit settings" in the open action menu + Then the field "Preconfigured tool" matches value "My course tool" + And I follow "Edit preconfigured tool" + And I switch to "edit_tool" window + Then the field "Tool URL" matches value "http://www.example.com/lti/provider.php" + And the field "Consumer key" matches value "my key" + And the field "Shared secret" matches value "my secret" + And the field "Default launch container" matches value "Existing window" + And I press "Cancel" + And I switch to the main window diff --git a/mod/lti/tests/behat/config_test.feature b/mod/lti/tests/behat/config_test.feature deleted file mode 100644 index 32c6245b130..00000000000 --- a/mod/lti/tests/behat/config_test.feature +++ /dev/null @@ -1,35 +0,0 @@ -@mod @mod_lti @core_backup @javascript -Feature: Restoring Moodle 2 backup restores LTI configuration - - Background: - Given the following "courses" exist: - | fullname | shortname | category | idnumber | - | Course 1 | C1 | 0 | C1 | - And I log in as "admin" - - Scenario: Backup and restore course 1 - Given I navigate to "Manage external tool types" node in "Site administration > Plugins > Activity modules > LTI" - And I follow "Add external tool configuration" - And I set the following fields to these values: - | Tool name | Site Netspot Tool | - | Tool base URL | http://www.netspot.com.au | - | lti_coursevisible | 1 | - And I press "Save changes" - And I wait to be redirected - Given I am on site homepage - Then I follow "Course 1" - Then I turn editing mode on - And I add a "External tool" to section "1" and I fill the form with: - | Activity name | Site Netspot Tool | - | External tool type | Site Netspot Tool | - | Launch container | Embed | - And I follow "Course 1" - Then I should see "Site Netspot Tool" - Then 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: - Then I am on site homepage - And I follow "Course 1 copy 1" - And I open "Site Netspot Tool" actions menu - And I click on "Edit settings" "link" in the "Site Netspot Tool" activity - Then the field "External tool type" matches value "Site Netspot Tool" \ No newline at end of file From 8d41ccfbe174b806be5fab849f4d0b7b48b8a36a Mon Sep 17 00:00:00 2001 From: Marina Glancy Date: Tue, 17 May 2016 11:13:37 +0800 Subject: [PATCH 4/4] MDL-34161 mod_lti: backup and restore of submissions --- .../backup/moodle2/backup_lti_stepslib.php | 19 ++++++++++++ .../backup/moodle2/restore_lti_stepslib.php | 31 +++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/mod/lti/backup/moodle2/backup_lti_stepslib.php b/mod/lti/backup/moodle2/backup_lti_stepslib.php index 34893c1d82e..7b4b45760c4 100644 --- a/mod/lti/backup/moodle2/backup_lti_stepslib.php +++ b/mod/lti/backup/moodle2/backup_lti_stepslib.php @@ -133,6 +133,17 @@ class backup_lti_activity_structure_step extends backup_activity_structure_step ) ); + $ltisubmissions = new backup_nested_element('ltisubmissions'); + $ltisubmission = new backup_nested_element('ltisubmission', array('id'), array( + 'userid', + 'datesubmitted', + 'dateupdated', + 'gradepercent', + 'originalgrade', + 'launchid', + 'state' + )); + // Build the tree $lti->add_child($ltitype); $ltitype->add_child($ltitypesconfigs); @@ -141,6 +152,8 @@ class backup_lti_activity_structure_step extends backup_activity_structure_step $ltitype->add_child($ltitoolproxy); $ltitoolproxy->add_child($ltitoolsettings); $ltitoolsettings->add_child($ltitoolsetting); + $lti->add_child($ltisubmissions); + $ltisubmissions->add_child($ltisubmission); // Define sources. $ltirecord = $DB->get_record('lti', ['id' => $this->task->get_activityid()]); @@ -173,9 +186,15 @@ class backup_lti_activity_structure_step extends backup_activity_structure_step $ltitoolproxy->set_source_array([]); } + // All the rest of elements only happen if we are including user info. + if ($userinfo) { + $ltisubmission->set_source_table('lti_submission', array('ltiid' => backup::VAR_ACTIVITYID)); + } + // Define id annotations $ltitype->annotate_ids('user', 'createdby'); $ltitype->annotate_ids('course', 'course'); + $ltisubmission->annotate_ids('user', 'userid'); // Define file annotations. $lti->annotate_files('mod_lti', 'intro', null); // This file areas haven't itemid. diff --git a/mod/lti/backup/moodle2/restore_lti_stepslib.php b/mod/lti/backup/moodle2/restore_lti_stepslib.php index 7a542e7333c..2615c12e95c 100644 --- a/mod/lti/backup/moodle2/restore_lti_stepslib.php +++ b/mod/lti/backup/moodle2/restore_lti_stepslib.php @@ -59,6 +59,9 @@ class restore_lti_activity_structure_step extends restore_activity_structure_ste protected function define_structure() { $paths = array(); + // To know if we are including userinfo. + $userinfo = $this->get_setting_value('userinfo'); + $lti = new restore_path_element('lti', '/activity/lti'); $paths[] = $lti; $paths[] = new restore_path_element('ltitype', '/activity/lti/ltitype'); @@ -68,6 +71,11 @@ class restore_lti_activity_structure_step extends restore_activity_structure_ste $paths[] = new restore_path_element('ltitoolproxy', '/activity/lti/ltitype/ltitoolproxy'); $paths[] = new restore_path_element('ltitoolsetting', '/activity/lti/ltitype/ltitoolproxy/ltitoolsettings/ltitoolsetting'); + if ($userinfo) { + $submission = new restore_path_element('ltisubmission', '/activity/lti/ltisubmissions/ltisubmission'); + $paths[] = $submission; + } + // Add support for subplugin structures. $this->add_subplugin_structure('ltisource', $lti); $this->add_subplugin_structure('ltiservice', $lti); @@ -243,6 +251,29 @@ class restore_lti_activity_structure_step extends restore_activity_structure_ste $DB->insert_record('lti_tool_settings', $data); } + /** + * Process a submission restore + * @param mixed $data The data from backup XML file + */ + protected function process_ltisubmission($data) { + global $DB; + + $data = (object)$data; + $oldid = $data->id; + + $data->ltiid = $this->get_new_parentid('lti'); + + $data->datesubmitted = $this->apply_date_offset($data->datesubmitted); + $data->dateupdated = $this->apply_date_offset($data->dateupdated); + if ($data->userid > 0) { + $data->userid = $this->get_mappingid('user', $data->userid); + } + + $newitemid = $DB->insert_record('lti_submission', $data); + + $this->set_mapping('ltisubmission', $oldid, $newitemid); + } + protected function after_execute() { // Add lti related files, no need to match by itemname (just internally handled context). $this->add_related_files('mod_lti', 'intro', null);