From 0a580ed98dd66a0bd6289330e95d052ef779664c Mon Sep 17 00:00:00 2001 From: Rajneel Totaram Date: Mon, 26 Jan 2026 16:51:52 +1200 Subject: [PATCH] MDL-87258 mod_lti: request access for PNA Since Chrome 142, public servers accessing resources on local networks gets blocked by default. Users need to allow access for such requests to proceed. This patch introduces a new config setting `ltiallowlocalnetwork`. This determines if the local-network-access flag should be added to LTI iframes so that Chrome users get prompted when a local network access is being made. --- public/mod/lti/amd/build/contentitem.min.js | 2 +- public/mod/lti/amd/build/contentitem.min.js.map | 2 +- public/mod/lti/amd/build/mod_form.min.js | 2 +- public/mod/lti/amd/build/mod_form.min.js.map | 2 +- public/mod/lti/amd/src/contentitem.js | 4 +++- public/mod/lti/amd/src/mod_form.js | 5 +++-- public/mod/lti/mod_form.php | 8 ++++++-- public/mod/lti/templates/contentitem.mustache | 3 ++- public/mod/lti/view.php | 8 +++++++- 9 files changed, 25 insertions(+), 11 deletions(-) diff --git a/public/mod/lti/amd/build/contentitem.min.js b/public/mod/lti/amd/build/contentitem.min.js index f1c62ece6f9..96fee1c057b 100644 --- a/public/mod/lti/amd/build/contentitem.min.js +++ b/public/mod/lti/amd/build/contentitem.min.js @@ -9,6 +9,6 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since 3.2 */ -define("mod_lti/contentitem",["jquery","core/notification","core/str","core/templates","mod_lti/form-field","core/modal","core/modal_events"],(function($,notification,str,templates,FormField,Modal,ModalEvents){var dialogue,doneCallback,contentItem={init:function(url,postData,cb){doneCallback=cb;var context={url:url,postData:postData},bodyPromise=templates.render("mod_lti/contentitem",context);if(dialogue)return dialogue.setBody(bodyPromise),void dialogue.show();str.get_string("selectcontent","lti").then((function(title){return Modal.create({title:title,body:bodyPromise,large:!0,show:!0})})).then((function(modal){dialogue=modal,modal.getRoot().on(ModalEvents.hidden,(function(){modal.setBody(""),notification.fetchNotifications()}))})).catch(notification.exception)}},ltiFormFields=[new FormField("name",FormField.TYPES.TEXT,!1,""),new FormField("introeditor",FormField.TYPES.EDITOR,!1,""),new FormField("toolurl",FormField.TYPES.TEXT,!0,""),new FormField("securetoolurl",FormField.TYPES.TEXT,!0,""),new FormField("instructorchoiceacceptgrades",FormField.TYPES.CHECKBOX,!0,!0),new FormField("instructorchoicesendname",FormField.TYPES.CHECKBOX,!0,!0),new FormField("instructorchoicesendemailaddr",FormField.TYPES.CHECKBOX,!0,!0),new FormField("instructorcustomparameters",FormField.TYPES.TEXT,!0,""),new FormField("icon",FormField.TYPES.TEXT,!0,""),new FormField("secureicon",FormField.TYPES.TEXT,!0,""),new FormField("launchcontainer",FormField.TYPES.SELECT,!0,0),new FormField("grade_modgrade_point",FormField.TYPES.TEXT,!1,""),new FormField("lineitemresourceid",FormField.TYPES.TEXT,!0,""),new FormField("lineitemtag",FormField.TYPES.TEXT,!0,""),new FormField("lineitemsubreviewurl",FormField.TYPES.TEXT,!0,""),new FormField("lineitemsubreviewparams",FormField.TYPES.TEXT,!0,"")];const hideElement=e=>{e.setAttribute("hidden","true"),e.setAttribute("aria-hidden","true"),e.setAttribute("tab-index","-1")},showElement=e=>{e.removeAttribute("hidden"),e.setAttribute("aria-hidden","false"),e.setAttribute("tab-index","1")};return window.processContentItemReturnData=function(returnData){var index;if(dialogue&&dialogue.hide(),returnData.multiple){for(index in ltiFormFields)ltiFormFields[index].setFieldValue("name"===ltiFormFields[index].name?"item":null);var variants=[];returnData.multiple.forEach((function(v){variants.push((config=>{const variant={};return["name","toolurl","securetoolurl","instructorcustomparameters","icon","secureicon","launchcontainer","lineitemresourceid","lineitemtag","lineitemsubreviewurl","lineitemsubreviewparams"].forEach((function(name){variant[name]=config[name]||""})),variant["introeditor[text]"]=config.introeditor?config.introeditor.text:"",variant["introeditor[format]"]=config.introeditor?config.introeditor.format:"",1===config.instructorchoiceacceptgrades?(variant.instructorchoiceacceptgrades="1",variant["grade[modgrade_point]"]=config.grade_modgrade_point||"100"):variant.instructorchoiceacceptgrades="0",variant})(v))})),async function(items){const form=document.querySelector("#region-main-box form"),toolArea=form.querySelector('[data-attribute="dynamic-import"]'),buttonGroup=form.querySelector("#fgroup_id_buttonar"),submitAndLaunch=form.querySelector("#id_submitbutton");Array.from(form.children).forEach(hideElement),hideElement(submitAndLaunch);const{html:html,js:js}=await templates.renderForPromise("mod_lti/tool_deeplinking_results",{items:items});await templates.replaceNodeContents(toolArea,html,js),showElement(toolArea),showElement(buttonGroup)}(returnData.multiple);const submitAndCourse=document.querySelector("#id_submitbutton2");submitAndCourse.onclick=e=>{e.preventDefault(),submitAndCourse.disabled=!0;const fd=new FormData(document.querySelector("#region-main-box form")),backToCourse=()=>{document.querySelector("#id_cancel").click()};variants.reduce(((promise,variant)=>{Object.entries(variant).forEach((entry=>fd.set(entry[0],entry[1])));const body=new URLSearchParams(fd),doPost=()=>fetch(document.location.pathname,{method:"post",body:body});return promise.then(doPost).catch(doPost)}),Promise.resolve()).then(backToCourse).catch(backToCourse)}}else{for(index in ltiFormFields){var field=ltiFormFields[index],value=null;void 0!==returnData[field.name]&&(value=returnData[field.name]),field.setFieldValue(value)}field.setFieldValue(value),document.querySelector("#id_selectcontentindicator").innerHTML=returnData.selectcontentindicator}doneCallback&&doneCallback(returnData)},contentItem})); +define("mod_lti/contentitem",["jquery","core/notification","core/str","core/templates","mod_lti/form-field","core/modal","core/modal_events"],(function($,notification,str,templates,FormField,Modal,ModalEvents){var dialogue,doneCallback,contentItem={init:function(url,toolUrl,postData,cb){doneCallback=cb;var context={url:url,toolUrl:toolUrl,postData:postData},bodyPromise=templates.render("mod_lti/contentitem",context);if(dialogue)return dialogue.setBody(bodyPromise),void dialogue.show();str.get_string("selectcontent","lti").then((function(title){return Modal.create({title:title,body:bodyPromise,large:!0,show:!0})})).then((function(modal){dialogue=modal,modal.getRoot().on(ModalEvents.hidden,(function(){modal.setBody(""),notification.fetchNotifications()}))})).catch(notification.exception)}},ltiFormFields=[new FormField("name",FormField.TYPES.TEXT,!1,""),new FormField("introeditor",FormField.TYPES.EDITOR,!1,""),new FormField("toolurl",FormField.TYPES.TEXT,!0,""),new FormField("securetoolurl",FormField.TYPES.TEXT,!0,""),new FormField("instructorchoiceacceptgrades",FormField.TYPES.CHECKBOX,!0,!0),new FormField("instructorchoicesendname",FormField.TYPES.CHECKBOX,!0,!0),new FormField("instructorchoicesendemailaddr",FormField.TYPES.CHECKBOX,!0,!0),new FormField("instructorcustomparameters",FormField.TYPES.TEXT,!0,""),new FormField("icon",FormField.TYPES.TEXT,!0,""),new FormField("secureicon",FormField.TYPES.TEXT,!0,""),new FormField("launchcontainer",FormField.TYPES.SELECT,!0,0),new FormField("grade_modgrade_point",FormField.TYPES.TEXT,!1,""),new FormField("lineitemresourceid",FormField.TYPES.TEXT,!0,""),new FormField("lineitemtag",FormField.TYPES.TEXT,!0,""),new FormField("lineitemsubreviewurl",FormField.TYPES.TEXT,!0,""),new FormField("lineitemsubreviewparams",FormField.TYPES.TEXT,!0,"")];const hideElement=e=>{e.setAttribute("hidden","true"),e.setAttribute("aria-hidden","true"),e.setAttribute("tab-index","-1")},showElement=e=>{e.removeAttribute("hidden"),e.setAttribute("aria-hidden","false"),e.setAttribute("tab-index","1")};return window.processContentItemReturnData=function(returnData){var index;if(dialogue&&dialogue.hide(),returnData.multiple){for(index in ltiFormFields)ltiFormFields[index].setFieldValue("name"===ltiFormFields[index].name?"item":null);var variants=[];returnData.multiple.forEach((function(v){variants.push((config=>{const variant={};return["name","toolurl","securetoolurl","instructorcustomparameters","icon","secureicon","launchcontainer","lineitemresourceid","lineitemtag","lineitemsubreviewurl","lineitemsubreviewparams"].forEach((function(name){variant[name]=config[name]||""})),variant["introeditor[text]"]=config.introeditor?config.introeditor.text:"",variant["introeditor[format]"]=config.introeditor?config.introeditor.format:"",1===config.instructorchoiceacceptgrades?(variant.instructorchoiceacceptgrades="1",variant["grade[modgrade_point]"]=config.grade_modgrade_point||"100"):variant.instructorchoiceacceptgrades="0",variant})(v))})),async function(items){const form=document.querySelector("#region-main-box form"),toolArea=form.querySelector('[data-attribute="dynamic-import"]'),buttonGroup=form.querySelector("#fgroup_id_buttonar"),submitAndLaunch=form.querySelector("#id_submitbutton");Array.from(form.children).forEach(hideElement),hideElement(submitAndLaunch);const{html:html,js:js}=await templates.renderForPromise("mod_lti/tool_deeplinking_results",{items:items});await templates.replaceNodeContents(toolArea,html,js),showElement(toolArea),showElement(buttonGroup)}(returnData.multiple);const submitAndCourse=document.querySelector("#id_submitbutton2");submitAndCourse.onclick=e=>{e.preventDefault(),submitAndCourse.disabled=!0;const fd=new FormData(document.querySelector("#region-main-box form")),backToCourse=()=>{document.querySelector("#id_cancel").click()};variants.reduce(((promise,variant)=>{Object.entries(variant).forEach((entry=>fd.set(entry[0],entry[1])));const body=new URLSearchParams(fd),doPost=()=>fetch(document.location.pathname,{method:"post",body:body});return promise.then(doPost).catch(doPost)}),Promise.resolve()).then(backToCourse).catch(backToCourse)}}else{for(index in ltiFormFields){var field=ltiFormFields[index],value=null;void 0!==returnData[field.name]&&(value=returnData[field.name]),field.setFieldValue(value)}field.setFieldValue(value),document.querySelector("#id_selectcontentindicator").innerHTML=returnData.selectcontentindicator}doneCallback&&doneCallback(returnData)},contentItem})); //# sourceMappingURL=contentitem.min.js.map \ No newline at end of file diff --git a/public/mod/lti/amd/build/contentitem.min.js.map b/public/mod/lti/amd/build/contentitem.min.js.map index 6139f62171b..9d5465a0e21 100644 --- a/public/mod/lti/amd/build/contentitem.min.js.map +++ b/public/mod/lti/amd/build/contentitem.min.js.map @@ -1 +1 @@ -{"version":3,"file":"contentitem.min.js","sources":["../src/contentitem.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Launches the modal dialogue that contains the iframe that sends the Content-Item selection request to an\n * LTI tool provider that supports Content-Item type message.\n *\n * See template: mod_lti/contentitem\n *\n * @module mod_lti/contentitem\n * @copyright 2016 Jun Pataleta \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 3.2\n */\ndefine(\n [\n 'jquery',\n 'core/notification',\n 'core/str',\n 'core/templates',\n 'mod_lti/form-field',\n 'core/modal',\n 'core/modal_events'\n ],\n function($, notification, str, templates, FormField, Modal, ModalEvents) {\n var dialogue;\n var doneCallback;\n var contentItem = {\n /**\n * Init function.\n *\n * @param {string} url The URL for the content item selection.\n * @param {object} postData The data to be sent for the content item selection request.\n * @param {Function} cb The callback to run once the content item has been processed.\n */\n init: function(url, postData, cb) {\n doneCallback = cb;\n var context = {\n url: url,\n postData: postData\n };\n var bodyPromise = templates.render('mod_lti/contentitem', context);\n\n if (dialogue) {\n // Set dialogue body.\n dialogue.setBody(bodyPromise);\n // Display the dialogue.\n dialogue.show();\n return;\n }\n\n str.get_string('selectcontent', 'lti').then(function(title) {\n return Modal.create({\n title: title,\n body: bodyPromise,\n large: true,\n show: true,\n });\n }).then(function(modal) {\n dialogue = modal;\n // On hide handler.\n modal.getRoot().on(ModalEvents.hidden, function() {\n // Empty modal contents when it's hidden.\n modal.setBody('');\n\n // Fetch notifications.\n notification.fetchNotifications();\n });\n return;\n }).catch(notification.exception);\n }\n };\n\n /**\n * Array of form fields for LTI tool configuration.\n */\n var ltiFormFields = [\n new FormField('name', FormField.TYPES.TEXT, false, ''),\n new FormField('introeditor', FormField.TYPES.EDITOR, false, ''),\n new FormField('toolurl', FormField.TYPES.TEXT, true, ''),\n new FormField('securetoolurl', FormField.TYPES.TEXT, true, ''),\n new FormField('instructorchoiceacceptgrades', FormField.TYPES.CHECKBOX, true, true),\n new FormField('instructorchoicesendname', FormField.TYPES.CHECKBOX, true, true),\n new FormField('instructorchoicesendemailaddr', FormField.TYPES.CHECKBOX, true, true),\n new FormField('instructorcustomparameters', FormField.TYPES.TEXT, true, ''),\n new FormField('icon', FormField.TYPES.TEXT, true, ''),\n new FormField('secureicon', FormField.TYPES.TEXT, true, ''),\n new FormField('launchcontainer', FormField.TYPES.SELECT, true, 0),\n new FormField('grade_modgrade_point', FormField.TYPES.TEXT, false, ''),\n new FormField('lineitemresourceid', FormField.TYPES.TEXT, true, ''),\n new FormField('lineitemtag', FormField.TYPES.TEXT, true, ''),\n new FormField('lineitemsubreviewurl', FormField.TYPES.TEXT, true, ''),\n new FormField('lineitemsubreviewparams', FormField.TYPES.TEXT, true, '')\n ];\n\n /**\n * Hide the element, including aria and tab index.\n * @param {HTMLElement} e the element to be hidden.\n */\n const hideElement = (e) => {\n e.setAttribute('hidden', 'true');\n e.setAttribute('aria-hidden', 'true');\n e.setAttribute('tab-index', '-1');\n };\n\n /**\n * Show the element, including aria and tab index (set to 1).\n * @param {HTMLElement} e the element to be shown.\n */\n const showElement = (e) => {\n e.removeAttribute('hidden');\n e.setAttribute('aria-hidden', 'false');\n e.setAttribute('tab-index', '1');\n };\n\n /**\n * When more than one item needs to be added, the UI is simplified\n * to just list the items to be added. Form is hidden and the only\n * options is (save and return to course) or cancel.\n * This function injects the summary to the form page, and hides\n * the unneeded elements.\n * @param {Object[]} items items to be added to the course.\n */\n const showMultipleSummaryAndHideForm = async function(items) {\n const form = document.querySelector('#region-main-box form');\n const toolArea = form.querySelector('[data-attribute=\"dynamic-import\"]');\n const buttonGroup = form.querySelector('#fgroup_id_buttonar');\n const submitAndLaunch = form.querySelector('#id_submitbutton');\n Array.from(form.children).forEach(hideElement);\n hideElement(submitAndLaunch);\n const {html, js} = await templates.renderForPromise('mod_lti/tool_deeplinking_results',\n {items: items});\n\n await templates.replaceNodeContents(toolArea, html, js);\n showElement(toolArea);\n showElement(buttonGroup);\n };\n\n /**\n * Transforms config values aimed at populating the lti mod form to JSON variant\n * which are used to insert more than one activity modules in one submit\n * by applying variation to the submitted form.\n * See /course/modedit.php.\n * @private\n * @param {Object} config transforms a config to an actual form data to be posted.\n * @return {Object} variant that will be used to modify form values on submit.\n */\n var configToVariant = (config) => {\n const variant = {};\n ['name', 'toolurl', 'securetoolurl', 'instructorcustomparameters', 'icon', 'secureicon',\n 'launchcontainer', 'lineitemresourceid', 'lineitemtag', 'lineitemsubreviewurl',\n 'lineitemsubreviewparams'].forEach(\n function(name) {\n variant[name] = config[name] || '';\n }\n );\n variant['introeditor[text]'] = config.introeditor ? config.introeditor.text : '';\n variant['introeditor[format]'] = config.introeditor ? config.introeditor.format : '';\n if (config.instructorchoiceacceptgrades === 1) {\n variant.instructorchoiceacceptgrades = '1';\n variant['grade[modgrade_point]'] = config.grade_modgrade_point || '100';\n } else {\n variant.instructorchoiceacceptgrades = '0';\n }\n return variant;\n };\n\n /**\n * Window function that can be called from mod_lti/contentitem_return to close the dialogue and process the return data.\n * If the return data contains more than one item, the form will not be populated with item data\n * but rather hidden, and the item data will be added to a single input field used to create multiple\n * instances in one request.\n *\n * @param {object} returnData The fetched configuration data from the Content-Item selection dialogue.\n */\n window.processContentItemReturnData = function(returnData) {\n if (dialogue) {\n dialogue.hide();\n }\n var index;\n if (returnData.multiple) {\n for (index in ltiFormFields) {\n // Name is required, so putting a placeholder as it will not be used\n // in multi-items add.\n ltiFormFields[index].setFieldValue(ltiFormFields[index].name === 'name' ? 'item' : null);\n }\n var variants = [];\n returnData.multiple.forEach(function(v) {\n variants.push(configToVariant(v));\n });\n showMultipleSummaryAndHideForm(returnData.multiple);\n const submitAndCourse = document.querySelector('#id_submitbutton2');\n submitAndCourse.onclick = (e) => {\n e.preventDefault();\n submitAndCourse.disabled = true;\n const fd = new FormData(document.querySelector('#region-main-box form'));\n const postVariant = (promise, variant) => {\n Object.entries(variant).forEach((entry) => fd.set(entry[0], entry[1]));\n const body = new URLSearchParams(fd);\n const doPost = () => fetch(document.location.pathname, {method: 'post', body});\n return promise.then(doPost).catch(doPost);\n };\n const backToCourse = () => {\n document.querySelector(\"#id_cancel\").click();\n };\n variants.reduce(postVariant, Promise.resolve()).then(backToCourse).catch(backToCourse);\n };\n } else {\n // Populate LTI configuration fields from return data.\n for (index in ltiFormFields) {\n var field = ltiFormFields[index];\n var value = null;\n if (typeof returnData[field.name] !== 'undefined') {\n value = returnData[field.name];\n }\n field.setFieldValue(value);\n }\n field.setFieldValue(value);\n\n // Update the UI element which signifies content has been selected.\n document.querySelector(\"#id_selectcontentindicator\").innerHTML = returnData.selectcontentindicator;\n }\n\n if (doneCallback) {\n doneCallback(returnData);\n }\n };\n\n return contentItem;\n }\n);\n"],"names":["define","$","notification","str","templates","FormField","Modal","ModalEvents","dialogue","doneCallback","contentItem","init","url","postData","cb","context","bodyPromise","render","setBody","show","get_string","then","title","create","body","large","modal","getRoot","on","hidden","fetchNotifications","catch","exception","ltiFormFields","TYPES","TEXT","EDITOR","CHECKBOX","SELECT","hideElement","e","setAttribute","showElement","removeAttribute","window","processContentItemReturnData","returnData","index","hide","multiple","setFieldValue","name","variants","forEach","v","push","config","variant","introeditor","text","format","instructorchoiceacceptgrades","grade_modgrade_point","configToVariant","async","items","form","document","querySelector","toolArea","buttonGroup","submitAndLaunch","Array","from","children","html","js","renderForPromise","replaceNodeContents","showMultipleSummaryAndHideForm","submitAndCourse","onclick","preventDefault","disabled","fd","FormData","backToCourse","click","reduce","promise","Object","entries","entry","set","URLSearchParams","doPost","fetch","location","pathname","method","Promise","resolve","field","value","innerHTML","selectcontentindicator"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"contentitem.min.js","sources":["../src/contentitem.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Launches the modal dialogue that contains the iframe that sends the Content-Item selection request to an\n * LTI tool provider that supports Content-Item type message.\n *\n * See template: mod_lti/contentitem\n *\n * @module mod_lti/contentitem\n * @copyright 2016 Jun Pataleta \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 3.2\n */\ndefine(\n [\n 'jquery',\n 'core/notification',\n 'core/str',\n 'core/templates',\n 'mod_lti/form-field',\n 'core/modal',\n 'core/modal_events'\n ],\n function($, notification, str, templates, FormField, Modal, ModalEvents) {\n var dialogue;\n var doneCallback;\n var contentItem = {\n /**\n * Init function.\n *\n * @param {string} url The URL for the content item selection.\n * @param {string} toolUrl The URL of the LTI tool.\n * @param {object} postData The data to be sent for the content item selection request.\n * @param {Function} cb The callback to run once the content item has been processed.\n */\n init: function(url, toolUrl, postData, cb) {\n doneCallback = cb;\n var context = {\n url: url,\n toolUrl: toolUrl,\n postData: postData\n };\n var bodyPromise = templates.render('mod_lti/contentitem', context);\n\n if (dialogue) {\n // Set dialogue body.\n dialogue.setBody(bodyPromise);\n // Display the dialogue.\n dialogue.show();\n return;\n }\n\n str.get_string('selectcontent', 'lti').then(function(title) {\n return Modal.create({\n title: title,\n body: bodyPromise,\n large: true,\n show: true,\n });\n }).then(function(modal) {\n dialogue = modal;\n // On hide handler.\n modal.getRoot().on(ModalEvents.hidden, function() {\n // Empty modal contents when it's hidden.\n modal.setBody('');\n\n // Fetch notifications.\n notification.fetchNotifications();\n });\n return;\n }).catch(notification.exception);\n }\n };\n\n /**\n * Array of form fields for LTI tool configuration.\n */\n var ltiFormFields = [\n new FormField('name', FormField.TYPES.TEXT, false, ''),\n new FormField('introeditor', FormField.TYPES.EDITOR, false, ''),\n new FormField('toolurl', FormField.TYPES.TEXT, true, ''),\n new FormField('securetoolurl', FormField.TYPES.TEXT, true, ''),\n new FormField('instructorchoiceacceptgrades', FormField.TYPES.CHECKBOX, true, true),\n new FormField('instructorchoicesendname', FormField.TYPES.CHECKBOX, true, true),\n new FormField('instructorchoicesendemailaddr', FormField.TYPES.CHECKBOX, true, true),\n new FormField('instructorcustomparameters', FormField.TYPES.TEXT, true, ''),\n new FormField('icon', FormField.TYPES.TEXT, true, ''),\n new FormField('secureicon', FormField.TYPES.TEXT, true, ''),\n new FormField('launchcontainer', FormField.TYPES.SELECT, true, 0),\n new FormField('grade_modgrade_point', FormField.TYPES.TEXT, false, ''),\n new FormField('lineitemresourceid', FormField.TYPES.TEXT, true, ''),\n new FormField('lineitemtag', FormField.TYPES.TEXT, true, ''),\n new FormField('lineitemsubreviewurl', FormField.TYPES.TEXT, true, ''),\n new FormField('lineitemsubreviewparams', FormField.TYPES.TEXT, true, '')\n ];\n\n /**\n * Hide the element, including aria and tab index.\n * @param {HTMLElement} e the element to be hidden.\n */\n const hideElement = (e) => {\n e.setAttribute('hidden', 'true');\n e.setAttribute('aria-hidden', 'true');\n e.setAttribute('tab-index', '-1');\n };\n\n /**\n * Show the element, including aria and tab index (set to 1).\n * @param {HTMLElement} e the element to be shown.\n */\n const showElement = (e) => {\n e.removeAttribute('hidden');\n e.setAttribute('aria-hidden', 'false');\n e.setAttribute('tab-index', '1');\n };\n\n /**\n * When more than one item needs to be added, the UI is simplified\n * to just list the items to be added. Form is hidden and the only\n * options is (save and return to course) or cancel.\n * This function injects the summary to the form page, and hides\n * the unneeded elements.\n * @param {Object[]} items items to be added to the course.\n */\n const showMultipleSummaryAndHideForm = async function(items) {\n const form = document.querySelector('#region-main-box form');\n const toolArea = form.querySelector('[data-attribute=\"dynamic-import\"]');\n const buttonGroup = form.querySelector('#fgroup_id_buttonar');\n const submitAndLaunch = form.querySelector('#id_submitbutton');\n Array.from(form.children).forEach(hideElement);\n hideElement(submitAndLaunch);\n const {html, js} = await templates.renderForPromise('mod_lti/tool_deeplinking_results',\n {items: items});\n\n await templates.replaceNodeContents(toolArea, html, js);\n showElement(toolArea);\n showElement(buttonGroup);\n };\n\n /**\n * Transforms config values aimed at populating the lti mod form to JSON variant\n * which are used to insert more than one activity modules in one submit\n * by applying variation to the submitted form.\n * See /course/modedit.php.\n * @private\n * @param {Object} config transforms a config to an actual form data to be posted.\n * @return {Object} variant that will be used to modify form values on submit.\n */\n var configToVariant = (config) => {\n const variant = {};\n ['name', 'toolurl', 'securetoolurl', 'instructorcustomparameters', 'icon', 'secureicon',\n 'launchcontainer', 'lineitemresourceid', 'lineitemtag', 'lineitemsubreviewurl',\n 'lineitemsubreviewparams'].forEach(\n function(name) {\n variant[name] = config[name] || '';\n }\n );\n variant['introeditor[text]'] = config.introeditor ? config.introeditor.text : '';\n variant['introeditor[format]'] = config.introeditor ? config.introeditor.format : '';\n if (config.instructorchoiceacceptgrades === 1) {\n variant.instructorchoiceacceptgrades = '1';\n variant['grade[modgrade_point]'] = config.grade_modgrade_point || '100';\n } else {\n variant.instructorchoiceacceptgrades = '0';\n }\n return variant;\n };\n\n /**\n * Window function that can be called from mod_lti/contentitem_return to close the dialogue and process the return data.\n * If the return data contains more than one item, the form will not be populated with item data\n * but rather hidden, and the item data will be added to a single input field used to create multiple\n * instances in one request.\n *\n * @param {object} returnData The fetched configuration data from the Content-Item selection dialogue.\n */\n window.processContentItemReturnData = function(returnData) {\n if (dialogue) {\n dialogue.hide();\n }\n var index;\n if (returnData.multiple) {\n for (index in ltiFormFields) {\n // Name is required, so putting a placeholder as it will not be used\n // in multi-items add.\n ltiFormFields[index].setFieldValue(ltiFormFields[index].name === 'name' ? 'item' : null);\n }\n var variants = [];\n returnData.multiple.forEach(function(v) {\n variants.push(configToVariant(v));\n });\n showMultipleSummaryAndHideForm(returnData.multiple);\n const submitAndCourse = document.querySelector('#id_submitbutton2');\n submitAndCourse.onclick = (e) => {\n e.preventDefault();\n submitAndCourse.disabled = true;\n const fd = new FormData(document.querySelector('#region-main-box form'));\n const postVariant = (promise, variant) => {\n Object.entries(variant).forEach((entry) => fd.set(entry[0], entry[1]));\n const body = new URLSearchParams(fd);\n const doPost = () => fetch(document.location.pathname, {method: 'post', body});\n return promise.then(doPost).catch(doPost);\n };\n const backToCourse = () => {\n document.querySelector(\"#id_cancel\").click();\n };\n variants.reduce(postVariant, Promise.resolve()).then(backToCourse).catch(backToCourse);\n };\n } else {\n // Populate LTI configuration fields from return data.\n for (index in ltiFormFields) {\n var field = ltiFormFields[index];\n var value = null;\n if (typeof returnData[field.name] !== 'undefined') {\n value = returnData[field.name];\n }\n field.setFieldValue(value);\n }\n field.setFieldValue(value);\n\n // Update the UI element which signifies content has been selected.\n document.querySelector(\"#id_selectcontentindicator\").innerHTML = returnData.selectcontentindicator;\n }\n\n if (doneCallback) {\n doneCallback(returnData);\n }\n };\n\n return contentItem;\n }\n);\n"],"names":["define","$","notification","str","templates","FormField","Modal","ModalEvents","dialogue","doneCallback","contentItem","init","url","toolUrl","postData","cb","context","bodyPromise","render","setBody","show","get_string","then","title","create","body","large","modal","getRoot","on","hidden","fetchNotifications","catch","exception","ltiFormFields","TYPES","TEXT","EDITOR","CHECKBOX","SELECT","hideElement","e","setAttribute","showElement","removeAttribute","window","processContentItemReturnData","returnData","index","hide","multiple","setFieldValue","name","variants","forEach","v","push","config","variant","introeditor","text","format","instructorchoiceacceptgrades","grade_modgrade_point","configToVariant","async","items","form","document","querySelector","toolArea","buttonGroup","submitAndLaunch","Array","from","children","html","js","renderForPromise","replaceNodeContents","showMultipleSummaryAndHideForm","submitAndCourse","onclick","preventDefault","disabled","fd","FormData","backToCourse","click","reduce","promise","Object","entries","entry","set","URLSearchParams","doPost","fetch","location","pathname","method","Promise","resolve","field","value","innerHTML","selectcontentindicator"],"mappings":"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"} \ No newline at end of file diff --git a/public/mod/lti/amd/build/mod_form.min.js b/public/mod/lti/amd/build/mod_form.min.js index 12492433c4e..1e3ece51007 100644 --- a/public/mod/lti/amd/build/mod_form.min.js +++ b/public/mod/lti/amd/build/mod_form.min.js @@ -1,3 +1,3 @@ -define("mod_lti/mod_form",["exports","mod_lti/contentitem"],(function(_exports,_contentitem){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_contentitem=(obj=_contentitem)&&obj.__esModule?obj:{default:obj};var _default={init:courseId=>{const contentItemButton=document.querySelector('[name="selectcontent"]');contentItemButton&&contentItemButton.addEventListener("click",(()=>{const contentItemUrl=contentItemButton.getAttribute("data-contentitemurl"),contentItemId=document.querySelector("#hidden_typeid").value;if(contentItemId){const title=document.querySelector("#id_name").value.trim(),text=document.querySelector("#id_introeditor").value.trim(),postData={id:contentItemId,course:courseId,title:title,text:text};_contentitem.default.init(contentItemUrl,postData,(returnData=>{if(!returnData.multiple){const allowGrades=document.querySelector("#id_instructorchoiceacceptgrades");let allowGradesChangeEvent=new Event("change");if(allowGrades.dispatchEvent(allowGradesChangeEvent),allowGrades.checked){const gradeType=document.querySelector("#id_grade_modgrade_type");gradeType.value="point";let gradeTypeChangeEvent=new Event("change");gradeType.dispatchEvent(gradeTypeChangeEvent)}}}))}}))}};return _exports.default=_default,_exports.default})); +define("mod_lti/mod_form",["exports","mod_lti/contentitem"],(function(_exports,_contentitem){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_contentitem=(obj=_contentitem)&&obj.__esModule?obj:{default:obj};var _default={init:(courseId,toolUrl)=>{const contentItemButton=document.querySelector('[name="selectcontent"]');contentItemButton&&contentItemButton.addEventListener("click",(()=>{const contentItemUrl=contentItemButton.getAttribute("data-contentitemurl"),contentItemId=document.querySelector("#hidden_typeid").value;if(contentItemId){const title=document.querySelector("#id_name").value.trim(),text=document.querySelector("#id_introeditor").value.trim(),postData={id:contentItemId,course:courseId,title:title,text:text};_contentitem.default.init(contentItemUrl,toolUrl,postData,(returnData=>{if(!returnData.multiple){const allowGrades=document.querySelector("#id_instructorchoiceacceptgrades");let allowGradesChangeEvent=new Event("change");if(allowGrades.dispatchEvent(allowGradesChangeEvent),allowGrades.checked){const gradeType=document.querySelector("#id_grade_modgrade_type");gradeType.value="point";let gradeTypeChangeEvent=new Event("change");gradeType.dispatchEvent(gradeTypeChangeEvent)}}}))}}))}};return _exports.default=_default,_exports.default})); //# sourceMappingURL=mod_form.min.js.map \ No newline at end of file diff --git a/public/mod/lti/amd/build/mod_form.min.js.map b/public/mod/lti/amd/build/mod_form.min.js.map index d1c4c7d62cd..9ee7fd033af 100644 --- a/public/mod/lti/amd/build/mod_form.min.js.map +++ b/public/mod/lti/amd/build/mod_form.min.js.map @@ -1 +1 @@ -{"version":3,"file":"mod_form.min.js","sources":["../src/mod_form.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Event handlers for the mod_lti mod_form.\n *\n * @module mod_lti/mod_form\n * @copyright 2023 Jake Dallimore \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n\"use strict\";\n\nimport ContentItem from 'mod_lti/contentitem';\n\n/**\n * Initialise module.\n *\n * @param {int} courseId the course id.\n */\nconst init = (courseId) => {\n const contentItemButton = document.querySelector('[name=\"selectcontent\"]');\n\n if (!contentItemButton) {\n return;\n }\n\n contentItemButton.addEventListener('click', () => {\n const contentItemUrl = contentItemButton.getAttribute('data-contentitemurl');\n const contentItemId = document.querySelector('#hidden_typeid').value;\n if (contentItemId) {\n const title = document.querySelector('#id_name').value.trim();\n const text = document.querySelector('#id_introeditor').value.trim();\n const postData = {\n id: contentItemId,\n course: courseId,\n title: title,\n text: text\n };\n\n // The callback below is called after the content item has been returned and processed.\n ContentItem.init(contentItemUrl, postData, (returnData) => {\n if (!returnData.multiple) {\n // The state of the grade checkbox has already been set by processContentItemReturnData() but that\n // hasn't fired the click/change event required by formslib to show/hide the dependent grade fields.\n // Fire it now.\n const allowGrades = document.querySelector('#id_instructorchoiceacceptgrades');\n let allowGradesChangeEvent = new Event('change');\n allowGrades.dispatchEvent(allowGradesChangeEvent);\n\n // If the tool is set to accept grades, make sure \"Point\" is selected.\n if (allowGrades.checked) {\n const gradeType = document.querySelector('#id_grade_modgrade_type');\n gradeType.value = \"point\";\n let gradeTypeChangeEvent = new Event('change');\n gradeType.dispatchEvent(gradeTypeChangeEvent);\n }\n }\n });\n }\n });\n};\n\nexport default {\n init: init\n};\n"],"names":["init","courseId","contentItemButton","document","querySelector","addEventListener","contentItemUrl","getAttribute","contentItemId","value","title","trim","text","postData","id","course","returnData","multiple","allowGrades","allowGradesChangeEvent","Event","dispatchEvent","checked","gradeType","gradeTypeChangeEvent"],"mappings":"oQA2Ee,CACXA,KA5CUC,iBACJC,kBAAoBC,SAASC,cAAc,0BAE5CF,mBAILA,kBAAkBG,iBAAiB,SAAS,WAClCC,eAAiBJ,kBAAkBK,aAAa,uBAChDC,cAAgBL,SAASC,cAAc,kBAAkBK,SAC3DD,cAAe,OACTE,MAAQP,SAASC,cAAc,YAAYK,MAAME,OACjDC,KAAOT,SAASC,cAAc,mBAAmBK,MAAME,OACvDE,SAAW,CACbC,GAAIN,cACJO,OAAQd,SACRS,MAAOA,MACPE,KAAMA,2BAIEZ,KAAKM,eAAgBO,UAAWG,iBACnCA,WAAWC,SAAU,OAIhBC,YAAcf,SAASC,cAAc,wCACvCe,uBAAyB,IAAIC,MAAM,aACvCF,YAAYG,cAAcF,wBAGtBD,YAAYI,QAAS,OACfC,UAAYpB,SAASC,cAAc,2BACzCmB,UAAUd,MAAQ,YACde,qBAAuB,IAAIJ,MAAM,UACrCG,UAAUF,cAAcG"} \ No newline at end of file +{"version":3,"file":"mod_form.min.js","sources":["../src/mod_form.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Event handlers for the mod_lti mod_form.\n *\n * @module mod_lti/mod_form\n * @copyright 2023 Jake Dallimore \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n\"use strict\";\n\nimport ContentItem from 'mod_lti/contentitem';\n\n/**\n * Initialise module.\n *\n * @param {int} courseId the course id.\n * @param {string} toolUrl the external tool url.\n */\nconst init = (courseId, toolUrl) => {\n const contentItemButton = document.querySelector('[name=\"selectcontent\"]');\n\n if (!contentItemButton) {\n return;\n }\n\n contentItemButton.addEventListener('click', () => {\n const contentItemUrl = contentItemButton.getAttribute('data-contentitemurl');\n const contentItemId = document.querySelector('#hidden_typeid').value;\n if (contentItemId) {\n const title = document.querySelector('#id_name').value.trim();\n const text = document.querySelector('#id_introeditor').value.trim();\n const postData = {\n id: contentItemId,\n course: courseId,\n title: title,\n text: text\n };\n\n // The callback below is called after the content item has been returned and processed.\n ContentItem.init(contentItemUrl, toolUrl, postData, (returnData) => {\n if (!returnData.multiple) {\n // The state of the grade checkbox has already been set by processContentItemReturnData() but that\n // hasn't fired the click/change event required by formslib to show/hide the dependent grade fields.\n // Fire it now.\n const allowGrades = document.querySelector('#id_instructorchoiceacceptgrades');\n let allowGradesChangeEvent = new Event('change');\n allowGrades.dispatchEvent(allowGradesChangeEvent);\n\n // If the tool is set to accept grades, make sure \"Point\" is selected.\n if (allowGrades.checked) {\n const gradeType = document.querySelector('#id_grade_modgrade_type');\n gradeType.value = \"point\";\n let gradeTypeChangeEvent = new Event('change');\n gradeType.dispatchEvent(gradeTypeChangeEvent);\n }\n }\n });\n }\n });\n};\n\nexport default {\n init: init\n};\n"],"names":["init","courseId","toolUrl","contentItemButton","document","querySelector","addEventListener","contentItemUrl","getAttribute","contentItemId","value","title","trim","text","postData","id","course","returnData","multiple","allowGrades","allowGradesChangeEvent","Event","dispatchEvent","checked","gradeType","gradeTypeChangeEvent"],"mappings":"oQA4Ee,CACXA,KA5CS,CAACC,SAAUC,iBACdC,kBAAoBC,SAASC,cAAc,0BAE5CF,mBAILA,kBAAkBG,iBAAiB,SAAS,WAClCC,eAAiBJ,kBAAkBK,aAAa,uBAChDC,cAAgBL,SAASC,cAAc,kBAAkBK,SAC3DD,cAAe,OACTE,MAAQP,SAASC,cAAc,YAAYK,MAAME,OACjDC,KAAOT,SAASC,cAAc,mBAAmBK,MAAME,OACvDE,SAAW,CACbC,GAAIN,cACJO,OAAQf,SACRU,MAAOA,MACPE,KAAMA,2BAIEb,KAAKO,eAAgBL,QAASY,UAAWG,iBAC5CA,WAAWC,SAAU,OAIhBC,YAAcf,SAASC,cAAc,wCACvCe,uBAAyB,IAAIC,MAAM,aACvCF,YAAYG,cAAcF,wBAGtBD,YAAYI,QAAS,OACfC,UAAYpB,SAASC,cAAc,2BACzCmB,UAAUd,MAAQ,YACde,qBAAuB,IAAIJ,MAAM,UACrCG,UAAUF,cAAcG"} \ No newline at end of file diff --git a/public/mod/lti/amd/src/contentitem.js b/public/mod/lti/amd/src/contentitem.js index 96d4d905932..bb0ac1c94eb 100644 --- a/public/mod/lti/amd/src/contentitem.js +++ b/public/mod/lti/amd/src/contentitem.js @@ -42,13 +42,15 @@ define( * Init function. * * @param {string} url The URL for the content item selection. + * @param {string} toolUrl The URL of the LTI tool. * @param {object} postData The data to be sent for the content item selection request. * @param {Function} cb The callback to run once the content item has been processed. */ - init: function(url, postData, cb) { + init: function(url, toolUrl, postData, cb) { doneCallback = cb; var context = { url: url, + toolUrl: toolUrl, postData: postData }; var bodyPromise = templates.render('mod_lti/contentitem', context); diff --git a/public/mod/lti/amd/src/mod_form.js b/public/mod/lti/amd/src/mod_form.js index dcf77b1c8c9..a074f97c303 100644 --- a/public/mod/lti/amd/src/mod_form.js +++ b/public/mod/lti/amd/src/mod_form.js @@ -29,8 +29,9 @@ import ContentItem from 'mod_lti/contentitem'; * Initialise module. * * @param {int} courseId the course id. + * @param {string} toolUrl the external tool url. */ -const init = (courseId) => { +const init = (courseId, toolUrl) => { const contentItemButton = document.querySelector('[name="selectcontent"]'); if (!contentItemButton) { @@ -51,7 +52,7 @@ const init = (courseId) => { }; // The callback below is called after the content item has been returned and processed. - ContentItem.init(contentItemUrl, postData, (returnData) => { + ContentItem.init(contentItemUrl, toolUrl, postData, (returnData) => { if (!returnData.multiple) { // The state of the grade checkbox has already been set by processContentItemReturnData() but that // hasn't fired the click/change event required by formslib to show/hide the dependent grade fields. diff --git a/public/mod/lti/mod_form.php b/public/mod/lti/mod_form.php index f3c1a14f21e..f92ec900ec9 100644 --- a/public/mod/lti/mod_form.php +++ b/public/mod/lti/mod_form.php @@ -309,7 +309,7 @@ class mod_lti_mod_form extends moodleform_mod { } public function definition() { - global $PAGE, $OUTPUT, $COURSE; + global $CFG, $PAGE, $OUTPUT, $COURSE; if ($this->type) { component_callback("ltisource_$this->type", 'add_instance_hook'); @@ -517,7 +517,11 @@ class mod_lti_mod_form extends moodleform_mod { $this->add_action_buttons(); if ($supportscontentitemselection) { - $PAGE->requires->js_call_amd('mod_lti/mod_form', 'init', [$COURSE->id]); + $jsparams = [$COURSE->id]; + // If local-network-access is enabled, pass the tool URL as well. + $jsparams[] = (isset($CFG->ltiallowlocalnetwork) && $CFG->ltiallowlocalnetwork) ? $tooltype->baseurl : ''; + + $PAGE->requires->js_call_amd('mod_lti/mod_form', 'init', $jsparams); } } diff --git a/public/mod/lti/templates/contentitem.mustache b/public/mod/lti/templates/contentitem.mustache index eab9868d949..58162e1a671 100644 --- a/public/mod/lti/templates/contentitem.mustache +++ b/public/mod/lti/templates/contentitem.mustache @@ -32,6 +32,7 @@ Example context (json): { "url": "/", + "toolUrl": "http://external.tool.url", "postData": { "id": "1", "course": "1", @@ -48,7 +49,7 @@
-
diff --git a/public/mod/lti/view.php b/public/mod/lti/view.php index 47066989459..9c3e98b3298 100644 --- a/public/mod/lti/view.php +++ b/public/mod/lti/view.php @@ -187,7 +187,13 @@ if (($launchcontainer == LTI_LAUNCH_CONTAINER_WINDOW)) { "geolocation $ltiallow; " . "midi $ltiallow; " . "encrypted-media $ltiallow; " . - "autoplay $ltiallow"; + "autoplay $ltiallow;"; + + // Only allow local-network-access if it is enabled in config.php. + if (isset($CFG->ltiallowlocalnetwork) && $CFG->ltiallowlocalnetwork) { + $attributes['allow'] .= " local-network-access $ltiallow;"; + } + $attributes['allowfullscreen'] = 1; $iframehtml = html_writer::tag('iframe', $content, $attributes); echo $iframehtml;