MDL-67473 mod_lti: deeplinking multiple support

AMOS BEGIN
 CPY [contentitem_help,mod_lti],[contentitem_deeplinking_help,mod_lti]
AMOS END
This commit is contained in:
Claude Vervoort 2019-12-06 13:47:54 -05:00
parent 5486b031ee
commit 957da4b564
15 changed files with 553 additions and 123 deletions

View file

@ -1,2 +1,2 @@
define ("mod_lti/contentitem",["jquery","core/notification","core/str","core/templates","mod_lti/form-field","core/modal_factory","core/modal_events"],function(a,b,c,d,e,f,g){var h,i,j=[new e("name",e.TYPES.TEXT,!1,""),new e("introeditor",e.TYPES.EDITOR,!1,""),new e("toolurl",e.TYPES.TEXT,!0,""),new e("securetoolurl",e.TYPES.TEXT,!0,""),new e("instructorchoiceacceptgrades",e.TYPES.CHECKBOX,!0,!0),new e("instructorchoicesendname",e.TYPES.CHECKBOX,!0,!0),new e("instructorchoicesendemailaddr",e.TYPES.CHECKBOX,!0,!0),new e("instructorcustomparameters",e.TYPES.TEXT,!0,""),new e("icon",e.TYPES.TEXT,!0,""),new e("secureicon",e.TYPES.TEXT,!0,""),new e("launchcontainer",e.TYPES.SELECT,!0,0),new e("grade_modgrade_point",e.TYPES.TEXT,!1,""),new e("lineitemresourceid",e.TYPES.TEXT,!0,""),new e("lineitemtag",e.TYPES.TEXT,!0,"")];window.processContentItemReturnData=function(a){if(h){h.hide()}for(var b in j){var c=j[b],d=null;if("undefined"!=typeof a[c.name]){d=a[c.name]}c.setFieldValue(d)}if(i){i()}};return{init:function init(a,e,j){i=j;var k=d.render("mod_lti/contentitem",{url:a,postData:e});if(h){h.setBody(k);h.show();return}c.get_string("selectcontent","lti").then(function(a){return f.create({title:a,body:k,large:!0})}).then(function(a){h=a;a.getRoot().on(g.hidden,function(){a.setBody("");b.fetchNotifications()});a.show()}).catch(b.exception)}}});
function asyncGeneratorStep(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function _asyncToGenerator(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var h=a.apply(b,c);function f(a){asyncGeneratorStep(h,d,e,f,g,"next",a)}function g(a){asyncGeneratorStep(h,d,e,f,g,"throw",a)}f(void 0)})}}define ("mod_lti/contentitem",["jquery","core/notification","core/str","core/templates","mod_lti/form-field","core/modal_factory","core/modal_events"],function(a,b,c,d,e,f,g){var h,i,j=[new e("name",e.TYPES.TEXT,!1,""),new e("introeditor",e.TYPES.EDITOR,!1,""),new e("toolurl",e.TYPES.TEXT,!0,""),new e("securetoolurl",e.TYPES.TEXT,!0,""),new e("instructorchoiceacceptgrades",e.TYPES.CHECKBOX,!0,!0),new e("instructorchoicesendname",e.TYPES.CHECKBOX,!0,!0),new e("instructorchoicesendemailaddr",e.TYPES.CHECKBOX,!0,!0),new e("instructorcustomparameters",e.TYPES.TEXT,!0,""),new e("icon",e.TYPES.TEXT,!0,""),new e("secureicon",e.TYPES.TEXT,!0,""),new e("launchcontainer",e.TYPES.SELECT,!0,0),new e("grade_modgrade_point",e.TYPES.TEXT,!1,""),new e("lineitemresourceid",e.TYPES.TEXT,!0,""),new e("lineitemtag",e.TYPES.TEXT,!0,"")],k=function(a){a.setAttribute("hidden","true");a.setAttribute("aria-hidden","true");a.setAttribute("tab-index","-1")},l=function(a){a.removeAttribute("hidden");a.setAttribute("aria-hidden","false");a.setAttribute("tab-index","1")},m=function(){var a=_asyncToGenerator(regeneratorRuntime.mark(function a(b){var c,e,f,g,h,i,j;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:c=document.querySelector("#region-main-box form");e=c.querySelector("[data-attribute=\"dynamic-import\"]");f=c.querySelector("#fgroup_id_buttonar");g=c.querySelector("#id_submitbutton");Array.from(c.children).forEach(k);k(g);a.next=8;return d.renderForPromise("mod_lti/tool_deeplinking_results",{items:b});case 8:h=a.sent;i=h.html;j=h.js;a.next=13;return d.replaceNodeContents(e,i,j);case 13:l(e);l(f);case 15:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}(),n=function(a){var b={};["name","toolurl","securetoolurl","instructorcustomparameters","icon","secureicon","launchcontainer"].forEach(function(c){b[c]=a[c]||""});b["introeditor[text]"]=a.introeditor?a.introeditor.text:"";b["introeditor[format]"]=a.introeditor?a.introeditor.format:"";if(1===a.instructorchoiceacceptgrades){b.instructorchoiceacceptgrades="1";b["grade[modgrade_point]"]=a.grade_modgrade_point||"100"}else{b.instructorchoiceacceptgrades="0"}return b};window.processContentItemReturnData=function(a){if(h){h.hide()}if(a.multiple){for(var b in j){j[b].setFieldValue("name"===j[b].name?"item":null)}var c=[];a.multiple.forEach(function(a){c.push(n(a))});m(a.multiple);var f=document.querySelector("#id_submitbutton2");f.onclick=function(a){a.preventDefault();f.disabled=!0;var b=new FormData(document.querySelector("form.mform")),d=function(){document.querySelector("#id_cancel").click()};c.reduce(function postVariant(a,c){Object.entries(c).forEach(function(a){return b.set(a[0],a[1])});var d=new URLSearchParams(b),e=function(){return fetch(document.location.pathname,{method:"post",body:d})};return a.then(e).catch(e)},Promise.resolve()).then(d).catch(d)}}else{for(b in j){var d=j[b],e=null;if("undefined"!=typeof a[d.name]){e=a[d.name]}d.setFieldValue(e)}d.setFieldValue(e)}if(i){i(a)}};return{init:function init(a,e,j){i=j;var k=d.render("mod_lti/contentitem",{url:a,postData:e});if(h){h.setBody(k);h.show();return}c.get_string("selectcontent","lti").then(function(a){return f.create({title:a,body:k,large:!0})}).then(function(a){h=a;a.getRoot().on(g.hidden,function(){a.setBody("");b.fetchNotifications()});a.show()}).catch(b.exception)}}});
//# sourceMappingURL=contentitem.min.js.map

File diff suppressed because one or more lines are too long

View file

@ -109,8 +109,81 @@ define(
new FormField('lineitemtag', FormField.TYPES.TEXT, true, '')
];
/**
* Hide the element, including aria and tab index.
* @param {HTMLElement} e the element to be hidden.
*/
const hideElement = (e) => {
e.setAttribute('hidden', 'true');
e.setAttribute('aria-hidden', 'true');
e.setAttribute('tab-index', '-1');
};
/**
* Show the element, including aria and tab index (set to 1).
* @param {HTMLElement} e the element to be shown.
*/
const showElement = (e) => {
e.removeAttribute('hidden');
e.setAttribute('aria-hidden', 'false');
e.setAttribute('tab-index', '1');
};
/**
* When more than one item needs to be added, the UI is simplified
* to just list the items to be added. Form is hidden and the only
* options is (save and return to course) or cancel.
* This function injects the summary to the form page, and hides
* the unneeded elements.
* @param {Object[]} items items to be added to the course.
*/
const showMultipleSummaryAndHideForm = async function(items) {
const form = document.querySelector('#region-main-box form');
const toolArea = form.querySelector('[data-attribute="dynamic-import"]');
const buttonGroup = form.querySelector('#fgroup_id_buttonar');
const submitAndLaunch = form.querySelector('#id_submitbutton');
Array.from(form.children).forEach(hideElement);
hideElement(submitAndLaunch);
const {html, js} = await templates.renderForPromise('mod_lti/tool_deeplinking_results',
{items: items});
await templates.replaceNodeContents(toolArea, html, js);
showElement(toolArea);
showElement(buttonGroup);
};
/**
* Transforms config values aimed at populating the lti mod form to JSON variant
* which are used to insert more than one activity modules in one submit
* by applying variation to the submitted form.
* See /course/modedit.php.
* @private
* @param {Object} config transforms a config to an actual form data to be posted.
* @return {Object} variant that will be used to modify form values on submit.
*/
var configToVariant = (config) => {
const variant = {};
['name', 'toolurl', 'securetoolurl', 'instructorcustomparameters', 'icon', 'secureicon', 'launchcontainer'].forEach(
function(name) {
variant[name] = config[name] || '';
}
);
variant['introeditor[text]'] = config.introeditor ? config.introeditor.text : '';
variant['introeditor[format]'] = config.introeditor ? config.introeditor.format : '';
if (config.instructorchoiceacceptgrades === 1) {
variant.instructorchoiceacceptgrades = '1';
variant['grade[modgrade_point]'] = config.grade_modgrade_point || '100';
} else {
variant.instructorchoiceacceptgrades = '0';
}
return variant;
};
/**
* Window function that can be called from mod_lti/contentitem_return to close the dialogue and process the return data.
* If the return data contains more than one item, the form will not be populated with item data
* but rather hidden, and the item data will be added to a single input field used to create multiple
* instances in one request.
*
* @param {object} returnData The fetched configuration data from the Content-Item selection dialogue.
*/
@ -118,9 +191,36 @@ define(
if (dialogue) {
dialogue.hide();
}
// Populate LTI configuration fields from return data.
var index;
if (returnData.multiple) {
for (index in ltiFormFields) {
// Name is required, so putting a placeholder as it will not be used
// in multi-items add.
ltiFormFields[index].setFieldValue(ltiFormFields[index].name === 'name' ? 'item' : null);
}
var variants = [];
returnData.multiple.forEach(function(v) {
variants.push(configToVariant(v));
});
showMultipleSummaryAndHideForm(returnData.multiple);
const submitAndCourse = document.querySelector('#id_submitbutton2');
submitAndCourse.onclick = (e) => {
e.preventDefault();
submitAndCourse.disabled = true;
const fd = new FormData(document.querySelector('form.mform'));
const postVariant = (promise, variant) => {
Object.entries(variant).forEach((entry) => fd.set(entry[0], entry[1]));
const body = new URLSearchParams(fd);
const doPost = () => fetch(document.location.pathname, {method: 'post', body});
return promise.then(doPost).catch(doPost);
};
const backToCourse = () => {
document.querySelector("#id_cancel").click();
};
variants.reduce(postVariant, Promise.resolve()).then(backToCourse).catch(backToCourse);
};
} else {
// Populate LTI configuration fields from return data.
for (index in ltiFormFields) {
var field = ltiFormFields[index];
var value = null;
@ -129,9 +229,11 @@ define(
}
field.setFieldValue(value);
}
field.setFieldValue(value);
}
if (doneCallback) {
doneCallback();
doneCallback(returnData);
}
};

View file

@ -120,7 +120,7 @@ if ($ok) {
$title = base64_decode($titleb64);
$text = base64_decode($textb64);
$request = lti_build_content_item_selection_request($typeid, $course, $returnurl, $title, $text,
[], [], false, false, false, false, false, $nonce);
[], [], false, true, false, false, false, $nonce);
$endpoint = $request->url;
$params = $request->params;
}

View file

@ -214,9 +214,8 @@ class mod_lti_edit_types_form extends moodleform {
$mform->addHelpButton('lti_launchcontainer', 'default_launch_container', 'lti');
$mform->setType('lti_launchcontainer', PARAM_INT);
$mform->addElement('advcheckbox', 'lti_contentitem', get_string('contentitem', 'lti'));
$mform->addHelpButton('lti_contentitem', 'contentitem', 'lti');
$mform->setAdvanced('lti_contentitem');
$mform->addElement('advcheckbox', 'lti_contentitem', get_string('contentitem_deeplinking', 'lti'));
$mform->addHelpButton('lti_contentitem', 'contentitem_deeplinking', 'lti');
if ($istool) {
$mform->disabledIf('lti_contentitem', null);
}
@ -224,7 +223,6 @@ class mod_lti_edit_types_form extends moodleform {
$mform->addElement('text', 'lti_toolurl_ContentItemSelectionRequest',
get_string('toolurl_contentitemselectionrequest', 'lti'), array('size' => '64'));
$mform->setType('lti_toolurl_ContentItemSelectionRequest', PARAM_URL);
$mform->setAdvanced('lti_toolurl_ContentItemSelectionRequest');
$mform->addHelpButton('lti_toolurl_ContentItemSelectionRequest', 'toolurl_contentitemselectionrequest', 'lti');
$mform->disabledIf('lti_toolurl_ContentItemSelectionRequest', 'lti_contentitem', 'notchecked');
if ($istool) {
@ -271,7 +269,12 @@ class mod_lti_edit_types_form extends moodleform {
// LTI Extensions.
// Add grading preferences fieldset where the tool is allowed to return grades.
$mform->addElement('select', 'lti_acceptgrades', get_string('accept_grades_admin', 'lti'), $options);
$gradeoptions = array();
$gradeoptions[] = get_string('never', 'lti');
$gradeoptions[] = get_string('always', 'lti');
$gradeoptions[] = get_string('delegate_tool', 'lti');
$mform->addElement('select', 'lti_acceptgrades', get_string('accept_grades_admin', 'lti'), $gradeoptions);
$mform->setType('lti_acceptgrades', PARAM_INT);
$mform->setDefault('lti_acceptgrades', '2');
$mform->addHelpButton('lti_acceptgrades', 'accept_grades_admin', 'lti');

View file

@ -1,3 +1,5 @@
leaveblank,mod_lti
organizationid,mod_lti
organizationid_help,mod_lti
contentitem,mod_lti
contentitem_help,mod_lti

View file

@ -108,8 +108,10 @@ $string['configtoolurl'] = 'Default remote tool URL';
$string['configtypes'] = 'Enable LTI applications';
$string['configured'] = 'Configured';
$string['confirmtoolactivation'] = 'Are you sure you would like to activate this tool?';
$string['contentitem'] = 'Content-Item Message';
$string['contentitem_help'] = 'If ticked, the option \'Select content\' will be available when adding an external tool.';
$string['contentitem_deeplinking'] = 'Supports Deep Linking (Content-Item Message)';
$string['contentitem_deeplinking_help'] = 'If ticked, the option \'Select content\' will be available when adding an external tool.';
$string['contentitem_multiple_description'] = 'The following items will be added to your course:';
$string['contentitem_multiple_graded'] = 'Graded activity (Maximum grade: {$a})';
$string['course_tool_types'] = 'Course tools';
$string['courseactivitiesorresources'] = 'Course activities or resources';
$string['courseid'] = 'Course ID number';
@ -141,6 +143,7 @@ real estate to the tool, and others provide a more integrated feel with the Mood
Depending on the browser, it will open in a new tab or a popup window.
It is possible that browsers will prevent the new window from opening.';
$string['delegate'] = 'Delegate to teacher';
$string['delegate_tool'] = 'As specified in Deep Linking definition or Delegate to teacher';
$string['delete'] = 'Delete';
$string['delete_confirmation'] = 'Are you sure you want to delete this preconfigured tool?';
$string['deletetype'] = 'Delete preconfigured tool';
@ -593,3 +596,8 @@ $string['organizationid'] = 'Organisation ID';
$string['organizationid_help'] = 'A unique identifier for this Moodle instance. Typically, the DNS name of the organisation is used.
If this field is left blank, the host name of this Moodle site will be used as the default value.';
// Deprecated since Moodle 3.10.
$string['contentitem'] = 'Content-Item Message';
$string['contentitem_help'] = 'If ticked, the option \'Select content\' will be available when adding an external tool.';

View file

@ -1083,7 +1083,7 @@ function lti_build_custom_parameters($toolproxy, $tool, $instance, $params, $cus
* @throws coding_exception For invalid media type and presentation target parameters.
*/
function lti_build_content_item_selection_request($id, $course, moodle_url $returnurl, $title = '', $text = '', $mediatypes = [],
$presentationtargets = [], $autocreate = false, $multiple = false,
$presentationtargets = [], $autocreate = false, $multiple = true,
$unsigned = false, $canconfirm = false, $copyadvice = false, $nonce = '') {
global $USER;
@ -1422,53 +1422,15 @@ function lti_verify_jwt_signature($typeid, $consumerkey, $jwtparam) {
}
/**
* Processes the tool provider's response to the ContentItemSelectionRequest and builds the configuration data from the
* selected content item. This configuration data can be then used when adding a tool into the course.
* Converts LTI 1.1 Content Item for LTI Link to Form data.
*
* @param int $typeid The tool type ID.
* @param string $messagetype The value for the lti_message_type parameter.
* @param string $ltiversion The value for the lti_version parameter.
* @param string $consumerkey The consumer key.
* @param string $contentitemsjson The JSON string for the content_items parameter.
* @return stdClass The array of module information objects.
* @throws moodle_exception
* @throws lti\OAuthException
* @param object $tool Tool for which the item is created for.
* @param object $typeconfig The tool configuration.
* @param object $item Item populated from JSON to be converted to Form form
*
* @return stdClass Form config for the item
*/
function lti_tool_configuration_from_content_item($typeid, $messagetype, $ltiversion, $consumerkey, $contentitemsjson) {
$tool = lti_get_type($typeid);
// Validate parameters.
if (!$tool) {
throw new moodle_exception('errortooltypenotfound', 'mod_lti');
}
// Check lti_message_type. Show debugging if it's not set to ContentItemSelection.
// No need to throw exceptions for now since lti_message_type does not seem to be used in this processing at the moment.
if ($messagetype !== 'ContentItemSelection') {
debugging("lti_message_type is invalid: {$messagetype}. It should be set to 'ContentItemSelection'.",
DEBUG_DEVELOPER);
}
// Check LTI versions from our side and the response's side. Show debugging if they don't match.
// No need to throw exceptions for now since LTI version does not seem to be used in this processing at the moment.
$expectedversion = $tool->ltiversion;
$islti2 = ($expectedversion === LTI_VERSION_2);
if ($ltiversion !== $expectedversion) {
debugging("lti_version from response does not match the tool's configuration. Tool: {$expectedversion}," .
" Response: {$ltiversion}", DEBUG_DEVELOPER);
}
$items = json_decode($contentitemsjson);
if (empty($items)) {
throw new moodle_exception('errorinvaliddata', 'mod_lti', '', $contentitemsjson);
}
if (!isset($items->{'@graph'}) || !is_array($items->{'@graph'}) || (count($items->{'@graph'}) > 1)) {
throw new moodle_exception('errorinvalidresponseformat', 'mod_lti');
}
$config = null;
if (!empty($items->{'@graph'})) {
$item = $items->{'@graph'}[0];
$typeconfig = lti_get_type_type_config($tool->id);
function content_item_to_form(object $tool, object $typeconfig, object $item) : stdClass {
$config = new stdClass();
$config->name = '';
if (isset($item->title)) {
@ -1482,6 +1444,11 @@ function lti_tool_configuration_from_content_item($typeid, $messagetype, $ltiver
'text' => $item->text,
'format' => FORMAT_PLAIN
];
} else {
$config->introeditor = [
'text' => '',
'format' => FORMAT_PLAIN
];
}
if (isset($item->icon->{'@id'})) {
$iconurl = new moodle_url($item->icon->{'@id'});
@ -1500,11 +1467,13 @@ function lti_tool_configuration_from_content_item($typeid, $messagetype, $ltiver
$config->typeid = $tool->id;
}
$config->instructorchoiceacceptgrades = LTI_SETTING_NEVER;
$islti2 = $tool->ltiversion === LTI_VERSION_2;
if (!$islti2 && isset($typeconfig->lti_acceptgrades)) {
$acceptgrades = $typeconfig->lti_acceptgrades;
if ($acceptgrades == LTI_SETTING_ALWAYS) {
// We create a line item regardless if the definition contains one or not.
$config->instructorchoiceacceptgrades = LTI_SETTING_ALWAYS;
$config->grade_modgrade_point = 100;
}
if ($acceptgrades == LTI_SETTING_DELEGATE || $acceptgrades == LTI_SETTING_ALWAYS) {
if (isset($item->lineItem)) {
@ -1550,7 +1519,68 @@ function lti_tool_configuration_from_content_item($typeid, $messagetype, $ltiver
}
$config->instructorcustomparameters = implode("\n", $customparameters);
}
// Including a JSON version of the form data to support adding many items in one submit.
$config->contentitemjson = json_encode($item);
return $config;
}
/**
* Processes the tool provider's response to the ContentItemSelectionRequest and builds the configuration data from the
* selected content item. This configuration data can be then used when adding a tool into the course.
*
* @param int $typeid The tool type ID.
* @param string $messagetype The value for the lti_message_type parameter.
* @param string $ltiversion The value for the lti_version parameter.
* @param string $consumerkey The consumer key.
* @param string $contentitemsjson The JSON string for the content_items parameter.
* @return stdClass The array of module information objects.
* @throws moodle_exception
* @throws lti\OAuthException
*/
function lti_tool_configuration_from_content_item($typeid, $messagetype, $ltiversion, $consumerkey, $contentitemsjson) {
$tool = lti_get_type($typeid);
// Validate parameters.
if (!$tool) {
throw new moodle_exception('errortooltypenotfound', 'mod_lti');
}
// Check lti_message_type. Show debugging if it's not set to ContentItemSelection.
// No need to throw exceptions for now since lti_message_type does not seem to be used in this processing at the moment.
if ($messagetype !== 'ContentItemSelection') {
debugging("lti_message_type is invalid: {$messagetype}. It should be set to 'ContentItemSelection'.",
DEBUG_DEVELOPER);
}
// Check LTI versions from our side and the response's side. Show debugging if they don't match.
// No need to throw exceptions for now since LTI version does not seem to be used in this processing at the moment.
$expectedversion = $tool->ltiversion;
$islti2 = ($expectedversion === LTI_VERSION_2);
if ($ltiversion !== $expectedversion) {
debugging("lti_version from response does not match the tool's configuration. Tool: {$expectedversion}," .
" Response: {$ltiversion}", DEBUG_DEVELOPER);
}
$items = json_decode($contentitemsjson);
if (empty($items)) {
throw new moodle_exception('errorinvaliddata', 'mod_lti', '', $contentitemsjson);
}
if (!isset($items->{'@graph'}) || !is_array($items->{'@graph'})) {
throw new moodle_exception('errorinvalidresponseformat', 'mod_lti');
}
$config = null;
$items = $items->{'@graph'};
if (!empty($items)) {
$typeconfig = lti_get_type_type_config($tool->id);
if (count($items) == 1) {
$config = content_item_to_form($tool, $typeconfig, $items[0]);
} else {
$multiple = [];
foreach ($items as $item) {
$multiple[] = content_item_to_form($tool, $typeconfig, $item);
}
$config = new stdClass();
$config->multiple = $multiple;
}
}
return $config;
}
@ -1587,7 +1617,35 @@ function lti_convert_content_items($param) {
$newitem->text = $item->html;
unset($newitem->html);
}
if (isset($item->presentation)) {
if (isset($item->iframe)) {
// DeepLinking allows multiple options to be declared as supported.
// We favor iframe over new window if both are specified.
$newitem->placementAdvice = new stdClass();
$newitem->placementAdvice->presentationDocumentTarget = 'iframe';
if (isset($item->iframe->width)) {
$newitem->placementAdvice->displayWidth = $item->iframe->width;
}
if (isset($item->iframe->height)) {
$newitem->placementAdvice->displayHeight = $item->iframe->height;
}
unset($newitem->iframe);
unset($newitem->window);
} else if (isset($item->window)) {
$newitem->placementAdvice = new stdClass();
$newitem->placementAdvice->presentationDocumentTarget = 'window';
if (isset($item->window->targetName)) {
$newitem->placementAdvice->windowTarget = $item->window->targetName;
}
if (isset($item->window->width)) {
$newitem->placementAdvice->displayWidth = $item->window->width;
}
if (isset($item->window->height)) {
$newitem->placementAdvice->displayHeight = $item->window->height;
}
unset($newitem->window);
} else if (isset($item->presentation)) {
// This may have been part of an early draft but is not in the final spec
// so keeping it around for now in case it's actually been used.
$newitem->placementAdvice = new stdClass();
if (isset($item->presentation->documentTarget)) {
$newitem->placementAdvice->presentationDocumentTarget = $item->presentation->documentTarget;

View file

@ -97,8 +97,10 @@
};
require(['mod_lti/contentitem'], function(contentitem) {
contentitem.init(contentItemUrl, postData, function() {
contentitem.init(contentItemUrl, postData, function(returnData) {
if (!returnData.multiple) {
M.mod_lti.editor.toggleGradeSection();
}
});
});
}

View file

@ -79,8 +79,11 @@ class mod_lti_mod_form extends moodleform_mod {
$this->typeid = 0;
$mform =& $this->_form;
// Adding the "general" fieldset, where all the common settings are shown.
$mform->addElement('html', "<div data-attribute='dynamic-import' hidden aria-hidden='true' role='alert'></div>");
$mform->addElement('header', 'general', get_string('general', 'form'));
// Adding the standard "name" field.
$mform->addElement('text', 'name', get_string('basicltiname', 'lti'), array('size' => '64'));
$mform->setType('name', PARAM_TEXT);

View file

@ -0,0 +1,59 @@
{{!
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/>.
}}
{{!
@template mod_lti/tool_deeplinking_results
This template lists the items that will be added to the course after
a deep linking flow.
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
*
Example context (json):
{
"items": [
{
"name": "Chapter 1",
"instructorchoiceacceptgrades": 0
},
{
"name": "Quiz 1",
"instructorchoiceacceptgrades": 1,
"grade_modgrade_point": 20.5
}
]
}
}}
<p>
{{#str}} contentitem_multiple_description, mod_lti {{/str}}
</p>
<ul>
{{#items}}
<li>
<strong>{{{name}}}</strong>
{{#instructorchoiceacceptgrades}}
<em>{{#str}} contentitem_multiple_graded , mod_lti, {{{grade_modgrade_point}}} {{/str}}</em>
{{/instructorchoiceacceptgrades}}
</li>
{{/items}}
</ul>

View file

@ -2,7 +2,7 @@
Feature: Content-Item support
In order to easily add activities and content in a course from an external tool
As a teacher
I need to utilise a tool that supports the Content-Item Message type
I need to utilise a tool that supports the Deep Linking (Content-Item Message) type
Background:
Given the following "users" exist:
@ -16,25 +16,25 @@ Feature: Content-Item support
| teacher1 | C1 | editingteacher |
And I log in as "admin"
And I navigate to "Plugins > Activity modules > External tool > Manage tools" in site administration
# Create tool type that supports content-item.
# Create tool type that supports deep linking.
And I follow "configure a tool manually"
And I set the field "Tool name" to "Teaching Tool 1"
And I set the field "Tool URL" to local url "/mod/lti/tests/fixtures/tool_provider.php"
And I set the field "Tool configuration usage" to "Show in activity chooser and as a preconfigured tool"
And I expand all fieldsets
And I set the field "Content-Item Message" to "1"
And I set the field "Supports Deep Linking (Content-Item Message)" to "1"
And I press "Save changes"
And I log out
@javascript
Scenario: Tool that supports Content-Item Message type should be able to configure a tool via the Select content button
Scenario: Tool that supports Deep Linking should be able to configure a tool via the Select content button
When I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
And I add a "Teaching Tool 1" to section "1"
Then the "Select content" "button" should be enabled
@javascript
Scenario: Editing a tool's settings that was configured from a preconfigured tool that supports Content-Item.
Scenario: Editing a tool's settings that was configured from a preconfigured tool that supports Deep Linking.
When I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
And I add a "Teaching Tool 1" to section "1"
@ -67,12 +67,12 @@ Feature: Content-Item support
And I set the field "Activity name" to "Test tool activity 1"
And the "Select content" "button" should be disabled
And the "Tool URL" "field" should be enabled
# Selecting a tool that supports content-item: Select content button - enabled, Tool URL - enabled.
# Selecting a tool that supports deep linking: Select content button - enabled, Tool URL - enabled.
And I set the field "Preconfigured tool" to "Teaching Tool 1"
And I set the field "Activity name" to "Test tool activity 1"
Then the "Select content" "button" should be enabled
And the "Tool URL" "field" should be enabled
# Selecting a tool that does not support content-item: Select content button - disabled, Tool URL - disabled.
# Selecting a tool that does not support deep linking: Select content button - disabled, Tool URL - disabled.
And I set the field "Preconfigured tool" to "Teaching Tool 2"
And I set the field "Activity name" to "Test tool activity 1"
And the "Select content" "button" should be disabled

View file

@ -1,8 +1,8 @@
@mod @mod_lti
Feature: Create/edit tool configuration that has Content-Item support
In order to provide external tools that support the Content-Item Message type for teachers and learners
Feature: Create/edit tool configuration that has Deep Linking support
In order to provide external tools that support Deep Linking for teachers and learners
As an admin
I need to be able to configure external tool registrations that support the Content-Item Message type.
I need to be able to configure external tool registrations that support Deep Linking.
Background:
Given I log in as "admin"
@ -20,13 +20,13 @@ Feature: Create/edit tool configuration that has Content-Item support
And I set the field "Tool URL" to local url "/mod/lti/tests/fixtures/tool_provider.php"
And I set the field "Tool configuration usage" to "Show in activity chooser and as a preconfigured tool"
And I expand all fieldsets
And I set the field "Content-Item Message" to "1"
And I set the field "Supports Deep Linking (Content-Item Message)" to "1"
And I press "Save changes"
And I follow "Edit"
And I expand all fieldsets
Then the field "Content-Item Message" matches value "1"
And I set the field "Content-Item Message" to "0"
Then the field "Supports Deep Linking (Content-Item Message)" matches value "1"
And I set the field "Supports Deep Linking (Content-Item Message)" to "0"
And I press "Save changes"
And I follow "Edit"
And I expand all fieldsets
And the field "Content-Item Message" matches value "0"
And the field "Supports Deep Linking (Content-Item Message)" matches value "0"

View file

@ -377,7 +377,7 @@ class mod_lti_locallib_testcase extends advanced_testcase {
$this->assertEquals('frame,iframe,window', $params['accept_presentation_document_targets']);
$this->assertEquals($returnurl->out(false), $params['content_item_return_url']);
$this->assertEquals('false', $params['accept_unsigned']);
$this->assertEquals('false', $params['accept_multiple']);
$this->assertEquals('true', $params['accept_multiple']);
$this->assertEquals('false', $params['accept_copy_advice']);
$this->assertEquals('false', $params['auto_create']);
$this->assertEquals($type->name, $params['title']);
@ -1185,7 +1185,6 @@ MwIDAQAB
*/
public function test_lti_verify_jwt_signature_no_public_key() {
$this->resetAfterTest();
$this->setAdminUser();
// Create a tool type, associated with that proxy.
@ -1214,7 +1213,28 @@ MwIDAQAB
'url' => 'http://example.com/messages/launch',
'title' => 'Test title',
'text' => 'Test text',
'frame' => []
'iframe' => []
];
$contentitems[] = [
'type' => 'ltiResourceLink',
'url' => 'http://example.com/messages/launch2',
'title' => 'Test title2',
'text' => 'Test text2',
'iframe' => [
'height' => 200,
'width' => 300
],
'window' => []
];
$contentitems[] = [
'type' => 'ltiResourceLink',
'url' => 'http://example.com/messages/launch3',
'title' => 'Test title3',
'text' => 'Test text3',
'window' => [
'targetName' => 'test-win',
'height' => 400
]
];
$contentitems = json_encode($contentitems);
@ -1231,18 +1251,186 @@ MwIDAQAB
$objgraph->url = 'http://example.com/messages/launch';
$objgraph->title = 'Test title';
$objgraph->text = 'Test text';
$objgraph->frame = [];
$objgraph->placementAdvice = new stdClass();
$objgraph->placementAdvice->presentationDocumentTarget = 'iframe';
$objgraph->{$strtype} = 'LtiLinkItem';
$objgraph->mediaType = 'application\/vnd.ims.lti.v1.ltilink';
$objgraph2 = new stdClass();
$objgraph2->url = 'http://example.com/messages/launch2';
$objgraph2->title = 'Test title2';
$objgraph2->text = 'Test text2';
$objgraph2->placementAdvice = new stdClass();
$objgraph2->placementAdvice->presentationDocumentTarget = 'iframe';
$objgraph2->placementAdvice->displayHeight = 200;
$objgraph2->placementAdvice->displayWidth = 300;
$objgraph2->{$strtype} = 'LtiLinkItem';
$objgraph2->mediaType = 'application\/vnd.ims.lti.v1.ltilink';
$objgraph3 = new stdClass();
$objgraph3->url = 'http://example.com/messages/launch3';
$objgraph3->title = 'Test title3';
$objgraph3->text = 'Test text3';
$objgraph3->placementAdvice = new stdClass();
$objgraph3->placementAdvice->presentationDocumentTarget = 'window';
$objgraph3->placementAdvice->displayHeight = 400;
$objgraph3->placementAdvice->windowTarget = 'test-win';
$objgraph3->{$strtype} = 'LtiLinkItem';
$objgraph3->mediaType = 'application\/vnd.ims.lti.v1.ltilink';
$expected = new stdClass();
$expected->{$strcontext} = 'http://purl.imsglobal.org/ctx/lti/v1/ContentItem';
$expected->{$strgraph} = [];
$expected->{$strgraph}[] = $objgraph;
$expected->{$strgraph}[] = $objgraph2;
$expected->{$strgraph}[] = $objgraph3;
$this->assertEquals($expected, $jsondecode);
}
/**
* Test adding a single gradable item through content item.
*/
public function test_lti_tool_configuration_from_content_item_single_gradable() {
$this->resetAfterTest();
$this->setAdminUser();
$type = new stdClass();
$type->name = "Test tool";
$type->baseurl = "http://example.com";
$config = new stdClass();
$config->lti_acceptgrades = LTI_SETTING_DELEGATE;
$typeid = lti_add_type($type, $config);
$generator = $this->getDataGenerator()->get_plugin_generator('mod_lti');
$contentitems = [];
$contentitems[] = [
'type' => 'ltiResourceLink',
'url' => 'http://example.com/messages/launch',
'title' => 'Test title',
'lineItem' => [
'resourceId' => 'r12345',
'tag' => 'final',
'scoreMaximum' => 10.0
],
'frame' => []
];
$contentitemsjson13 = json_encode($contentitems);
$json11 = lti_convert_content_items($contentitemsjson13);
$config = lti_tool_configuration_from_content_item($typeid,
'ContentItemSelection',
$type->ltiversion,
'ConsumerKey',
$json11);
$this->assertEquals($contentitems[0]['url'], $config->toolurl);
$this->assertEquals(LTI_SETTING_ALWAYS, $config->instructorchoiceacceptgrades);
$this->assertEquals($contentitems[0]['lineItem']['tag'], $config->lineitemtag);
$this->assertEquals($contentitems[0]['lineItem']['resourceId'], $config->lineitemresourceid);
$this->assertEquals($contentitems[0]['lineItem']['scoreMaximum'], $config->grade_modgrade_point);
}
/**
* Test adding multiple gradable items through content item.
*/
public function test_lti_tool_configuration_from_content_item_multiple() {
$this->resetAfterTest();
$this->setAdminUser();
$type = new stdClass();
$type->name = "Test tool";
$type->baseurl = "http://example.com";
$config = new stdClass();
$config->lti_acceptgrades = LTI_SETTING_DELEGATE;
$typeid = lti_add_type($type, $config);
$generator = $this->getDataGenerator()->get_plugin_generator('mod_lti');
$contentitems = [];
$contentitems[] = [
'type' => 'ltiResourceLink',
'url' => 'http://example.com/messages/launch',
'title' => 'Test title',
'text' => 'Test text',
'icon' => [
'url' => 'http://lti.example.com/image.jpg',
'width' => 100
],
'frame' => []
];
$contentitems[] = [
'type' => 'ltiResourceLink',
'url' => 'http://example.com/messages/launchgraded',
'title' => 'Test Graded',
'lineItem' => [
'resourceId' => 'r12345',
'tag' => 'final',
'scoreMaximum' => 10.0
],
'frame' => []
];
$contentitemsjson13 = json_encode($contentitems);
$json11 = lti_convert_content_items($contentitemsjson13);
$config = lti_tool_configuration_from_content_item($typeid,
'ContentItemSelection',
$type->ltiversion,
'ConsumerKey',
$json11);
$this->assertNotNull($config->multiple);
$this->assertEquals(2, count( $config->multiple ));
$this->assertEquals($contentitems[0]['title'], $config->multiple[0]->name);
$this->assertEquals($contentitems[0]['url'], $config->multiple[0]->toolurl);
$this->assertEquals(LTI_SETTING_NEVER, $config->multiple[0]->instructorchoiceacceptgrades);
$this->assertEquals($contentitems[1]['url'], $config->multiple[1]->toolurl);
$this->assertEquals(LTI_SETTING_ALWAYS, $config->multiple[1]->instructorchoiceacceptgrades);
$this->assertEquals($contentitems[1]['lineItem']['tag'], $config->multiple[1]->lineitemtag);
$this->assertEquals($contentitems[1]['lineItem']['resourceId'], $config->multiple[1]->lineitemresourceid);
$this->assertEquals($contentitems[1]['lineItem']['scoreMaximum'], $config->multiple[1]->grade_modgrade_point);
}
/**
* Test adding a single non gradable item through content item.
*/
public function test_lti_tool_configuration_from_content_item_single() {
$this->resetAfterTest();
$this->setAdminUser();
$type = new stdClass();
$type->name = "Test tool";
$type->baseurl = "http://example.com";
$config = new stdClass();
$typeid = lti_add_type($type, $config);
$generator = $this->getDataGenerator()->get_plugin_generator('mod_lti');
$contentitems = [];
$contentitems[] = [
'type' => 'ltiResourceLink',
'url' => 'http://example.com/messages/launch',
'title' => 'Test title',
'text' => 'Test text',
'icon' => [
'url' => 'http://lti.example.com/image.jpg',
'width' => 100
],
'frame' => []
];
$contentitemsjson13 = json_encode($contentitems);
$json11 = lti_convert_content_items($contentitemsjson13);
$config = lti_tool_configuration_from_content_item($typeid,
'ContentItemSelection',
$type->ltiversion,
'ConsumerKey',
$json11);
$this->assertEquals($contentitems[0]['title'], $config->name);
$this->assertEquals($contentitems[0]['text'], $config->introeditor['text']);
$this->assertEquals($contentitems[0]['url'], $config->toolurl);
$this->assertEquals($contentitems[0]['icon']['url'], $config->icon);
$this->assertEquals(LTI_SETTING_NEVER, $config->instructorchoiceacceptgrades);
}
/**
* Test lti_sign_jwt().
*/

View file

@ -1,5 +1,10 @@
This files describes API changes in the lti code.
=== 3.10 ===
* Select Content supports multiple, allowing a tool to return more than one link at a time.
Parameter multiple in function lti_build_content_item_selection_request() is now set to true.
=== 3.8 ===
* The following functions have been finally deprecated and can not be used anymore: