diff --git a/mod/lti/amd/build/course_tools_list.min.js b/mod/lti/amd/build/course_tools_list.min.js
index 991eba8c9d2..097ff13542f 100644
--- a/mod/lti/amd/build/course_tools_list.min.js
+++ b/mod/lti/amd/build/course_tools_list.min.js
@@ -1,3 +1,3 @@
-define("mod_lti/course_tools_list",["exports","core/notification","core/pending","core/ajax","core/toast","core/prefetch","core/str","core_table/dynamic","core_table/local/dynamic/selectors"],(function(_exports,_notification,_pending,_ajax,_toast,_prefetch,_str,_dynamic,Selectors){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_notification=_interopRequireDefault(_notification),_pending=_interopRequireDefault(_pending),_ajax=_interopRequireDefault(_ajax),Selectors=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(Selectors);_exports.init=()=>{(0,_prefetch.prefetchStrings)("mod_lti",["deletecoursetool","deletecoursetoolconfirm","coursetooldeleted"]),(0,_prefetch.prefetchStrings)("core",["delete"]),document.addEventListener("click",(event=>{const courseToolDelete=event.target.closest('[data-action="course-tool-delete"]');if(courseToolDelete){event.preventDefault();const triggerElement=courseToolDelete.closest(".dropdown").querySelector(".dropdown-toggle");_notification.default.saveCancelPromise((0,_str.get_string)("deletecoursetool","mod_lti"),(0,_str.get_string)("deletecoursetoolconfirm","mod_lti",courseToolDelete.dataset.courseToolName),(0,_str.get_string)("delete","core"),{triggerElement:triggerElement}).then((()=>{const pendingPromise=new _pending.default("mod_lti/course_tools:delete"),request={methodname:"mod_lti_delete_course_tool_type",args:{tooltypeid:courseToolDelete.dataset.courseToolId}};return _ajax.default.call([request])[0].then((0,_toast.add)((0,_str.get_string)("coursetooldeleted","mod_lti"))).then((()=>{const tableRoot=triggerElement.closest(Selectors.main.region);return(0,_dynamic.refreshTableContent)(tableRoot)})).then(pendingPromise.resolve).catch(_notification.default.exception)})).catch((()=>{}))}}))}}));
+define("mod_lti/course_tools_list",["exports","core/notification","core/pending","core/ajax","core/toast","core/str","core_table/dynamic","core_table/local/dynamic/selectors"],(function(_exports,_notification,_pending,_ajax,_toast,_str,_dynamic,Selectors){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_notification=_interopRequireDefault(_notification),_pending=_interopRequireDefault(_pending),_ajax=_interopRequireDefault(_ajax),Selectors=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(Selectors);_exports.init=()=>{document.addEventListener("click",(event=>{const courseToolDelete=event.target.closest('[data-action="course-tool-delete"]');if(courseToolDelete){event.preventDefault();const deleteBodyStringId=courseToolDelete.dataset.courseToolUsage>0?"deletecoursetoolwithusageconfirm":"deletecoursetoolconfirm",requiredStrings=[{key:"deletecoursetool",component:"mod_lti",param:courseToolDelete.dataset.courseToolName},{key:deleteBodyStringId,component:"mod_lti",param:courseToolDelete.dataset.courseToolName},{key:"delete",component:"core",param:courseToolDelete.dataset.courseToolName},{key:"coursetooldeleted",component:"mod_lti",param:courseToolDelete.dataset.courseToolName}],triggerElement=courseToolDelete.closest(".dropdown").querySelector(".dropdown-toggle");(0,_str.get_strings)(requiredStrings).then((_ref=>{let[modalTitle,modalBody,deleteLabel]=_ref;return _notification.default.deleteCancelPromise(modalTitle,modalBody,deleteLabel,{triggerElement:triggerElement})})).then((()=>{const pendingPromise=new _pending.default("mod_lti/course_tools:delete"),request={methodname:"mod_lti_delete_course_tool_type",args:{tooltypeid:courseToolDelete.dataset.courseToolId}};return _ajax.default.call([request])[0].then((0,_toast.add)((0,_str.get_string)("coursetooldeleted","mod_lti",courseToolDelete.dataset.courseToolName))).then((()=>{const tableRoot=triggerElement.closest(Selectors.main.region);return(0,_dynamic.refreshTableContent)(tableRoot)})).then(pendingPromise.resolve).catch(_notification.default.exception)})).catch((()=>{}))}}))}}));
//# sourceMappingURL=course_tools_list.min.js.map
\ No newline at end of file
diff --git a/mod/lti/amd/build/course_tools_list.min.js.map b/mod/lti/amd/build/course_tools_list.min.js.map
index 13e18320ae4..9a7005049b9 100644
--- a/mod/lti/amd/build/course_tools_list.min.js.map
+++ b/mod/lti/amd/build/course_tools_list.min.js.map
@@ -1 +1 @@
-{"version":3,"file":"course_tools_list.min.js","sources":["../src/course_tools_list.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 * Course LTI External tools list management.\n *\n * @module mod_lti/course_tools_list\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 Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport Ajax from 'core/ajax';\nimport {add as addToast} from 'core/toast';\nimport {prefetchStrings} from 'core/prefetch';\nimport {get_string as getString} from 'core/str';\nimport {refreshTableContent} from 'core_table/dynamic';\nimport * as Selectors from 'core_table/local/dynamic/selectors';\n\n/**\n * Initialise module.\n */\nexport const init = () => {\n prefetchStrings('mod_lti', [\n 'deletecoursetool',\n 'deletecoursetoolconfirm',\n 'coursetooldeleted'\n ]);\n\n prefetchStrings('core', [\n 'delete',\n ]);\n\n document.addEventListener('click', event => {\n\n const courseToolDelete = event.target.closest('[data-action=\"course-tool-delete\"]');\n if (courseToolDelete) {\n event.preventDefault();\n\n // Use triggerElement to return focus to the action menu toggle.\n const triggerElement = courseToolDelete.closest('.dropdown').querySelector('.dropdown-toggle');\n Notification.saveCancelPromise(\n getString('deletecoursetool', 'mod_lti'),\n getString('deletecoursetoolconfirm', 'mod_lti', courseToolDelete.dataset.courseToolName),\n getString('delete', 'core'),\n {triggerElement}\n ).then(() => {\n const pendingPromise = new Pending('mod_lti/course_tools:delete');\n\n const request = {\n methodname: 'mod_lti_delete_course_tool_type',\n args: {tooltypeid: courseToolDelete.dataset.courseToolId}\n };\n return Ajax.call([request])[0]\n .then(addToast(getString('coursetooldeleted', 'mod_lti')))\n .then(() => {\n const tableRoot = triggerElement.closest(Selectors.main.region);\n return refreshTableContent(tableRoot);\n })\n .then(pendingPromise.resolve)\n .catch(Notification.exception);\n }).catch(() => {\n return;\n });\n }\n });\n};\n"],"names":["document","addEventListener","event","courseToolDelete","target","closest","preventDefault","triggerElement","querySelector","saveCancelPromise","dataset","courseToolName","then","pendingPromise","Pending","request","methodname","args","tooltypeid","courseToolId","Ajax","call","tableRoot","Selectors","main","region","resolve","catch","Notification","exception"],"mappings":"8/CAqCoB,mCACA,UAAW,CACvB,mBACA,0BACA,oDAGY,OAAQ,CACpB,WAGJA,SAASC,iBAAiB,SAASC,cAEzBC,iBAAmBD,MAAME,OAAOC,QAAQ,yCAC1CF,iBAAkB,CAClBD,MAAMI,uBAGAC,eAAiBJ,iBAAiBE,QAAQ,aAAaG,cAAc,0CAC9DC,mBACT,mBAAU,mBAAoB,YAC9B,mBAAU,0BAA2B,UAAWN,iBAAiBO,QAAQC,iBACzE,mBAAU,SAAU,QACpB,CAACJ,eAAAA,iBACHK,MAAK,WACGC,eAAiB,IAAIC,iBAAQ,+BAE7BC,QAAU,CACZC,WAAY,kCACZC,KAAM,CAACC,WAAYf,iBAAiBO,QAAQS,sBAEzCC,cAAKC,KAAK,CAACN,UAAU,GACvBH,MAAK,eAAS,mBAAU,oBAAqB,aAC7CA,MAAK,WACIU,UAAYf,eAAeF,QAAQkB,UAAUC,KAAKC,eACjD,gCAAoBH,cAE9BV,KAAKC,eAAea,SACpBC,MAAMC,sBAAaC,cACzBF,OAAM"}
\ No newline at end of file
+{"version":3,"file":"course_tools_list.min.js","sources":["../src/course_tools_list.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 * Course LTI External tools list management.\n *\n * @module mod_lti/course_tools_list\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 Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport Ajax from 'core/ajax';\nimport {add as addToast} from 'core/toast';\nimport {get_string as getString, get_strings as getStrings} from 'core/str';\nimport {refreshTableContent} from 'core_table/dynamic';\nimport * as Selectors from 'core_table/local/dynamic/selectors';\n\n/**\n * Initialise module.\n */\nexport const init = () => {\n document.addEventListener('click', event => {\n\n const courseToolDelete = event.target.closest('[data-action=\"course-tool-delete\"]');\n if (courseToolDelete) {\n event.preventDefault();\n\n // A different message is used in the modal if the tool has usages within the course.\n const usage = courseToolDelete.dataset.courseToolUsage;\n const deleteBodyStringId = usage > 0 ? 'deletecoursetoolwithusageconfirm' : 'deletecoursetoolconfirm';\n const requiredStrings = [\n {key: 'deletecoursetool', component: 'mod_lti', param: courseToolDelete.dataset.courseToolName},\n {key: deleteBodyStringId, component: 'mod_lti', param: courseToolDelete.dataset.courseToolName},\n {key: 'delete', component: 'core', param: courseToolDelete.dataset.courseToolName},\n {key: 'coursetooldeleted', component: 'mod_lti', param: courseToolDelete.dataset.courseToolName}\n ];\n // Use triggerElement to return focus to the action menu toggle.\n const triggerElement = courseToolDelete.closest('.dropdown').querySelector('.dropdown-toggle');\n\n getStrings(requiredStrings).then(([modalTitle, modalBody, deleteLabel]) => {\n return Notification.deleteCancelPromise(\n modalTitle,\n modalBody,\n deleteLabel,\n {triggerElement});\n }).then(() => {\n const pendingPromise = new Pending('mod_lti/course_tools:delete');\n\n const request = {\n methodname: 'mod_lti_delete_course_tool_type',\n args: {tooltypeid: courseToolDelete.dataset.courseToolId}\n };\n return Ajax.call([request])[0]\n .then(addToast(getString('coursetooldeleted', 'mod_lti', courseToolDelete.dataset.courseToolName)))\n .then(() => {\n const tableRoot = triggerElement.closest(Selectors.main.region);\n return refreshTableContent(tableRoot);\n })\n .then(pendingPromise.resolve)\n .catch(Notification.exception);\n }).catch(() => {\n return;\n });\n }\n });\n};\n"],"names":["document","addEventListener","event","courseToolDelete","target","closest","preventDefault","deleteBodyStringId","dataset","courseToolUsage","requiredStrings","key","component","param","courseToolName","triggerElement","querySelector","then","_ref","modalTitle","modalBody","deleteLabel","Notification","deleteCancelPromise","pendingPromise","Pending","request","methodname","args","tooltypeid","courseToolId","Ajax","call","tableRoot","Selectors","main","region","resolve","catch","exception"],"mappings":"o+CAoCoB,KAChBA,SAASC,iBAAiB,SAASC,cAEzBC,iBAAmBD,MAAME,OAAOC,QAAQ,yCAC1CF,iBAAkB,CAClBD,MAAMI,uBAIAC,mBADQJ,iBAAiBK,QAAQC,gBACJ,EAAI,mCAAqC,0BACtEC,gBAAkB,CACpB,CAACC,IAAK,mBAAoBC,UAAW,UAAWC,MAAOV,iBAAiBK,QAAQM,gBAChF,CAACH,IAAKJ,mBAAoBK,UAAW,UAAWC,MAAOV,iBAAiBK,QAAQM,gBAChF,CAACH,IAAK,SAAUC,UAAW,OAAQC,MAAOV,iBAAiBK,QAAQM,gBACnE,CAACH,IAAK,oBAAqBC,UAAW,UAAWC,MAAOV,iBAAiBK,QAAQM,iBAG/EC,eAAiBZ,iBAAiBE,QAAQ,aAAaW,cAAc,yCAEhEN,iBAAiBO,MAAKC,WAAEC,WAAYC,UAAWC,yBAC/CC,sBAAaC,oBAChBJ,WACAC,UACAC,YACA,CAACN,eAAAA,oBACNE,MAAK,WACEO,eAAiB,IAAIC,iBAAQ,+BAE7BC,QAAU,CACZC,WAAY,kCACZC,KAAM,CAACC,WAAY1B,iBAAiBK,QAAQsB,sBAEzCC,cAAKC,KAAK,CAACN,UAAU,GACvBT,MAAK,eAAS,mBAAU,oBAAqB,UAAWd,iBAAiBK,QAAQM,kBACjFG,MAAK,WACIgB,UAAYlB,eAAeV,QAAQ6B,UAAUC,KAAKC,eACjD,gCAAoBH,cAE9BhB,KAAKO,eAAea,SACpBC,MAAMhB,sBAAaiB,cACzBD,OAAM"}
\ No newline at end of file
diff --git a/mod/lti/amd/src/course_tools_list.js b/mod/lti/amd/src/course_tools_list.js
index ca234c11436..9c16e2a2ef3 100644
--- a/mod/lti/amd/src/course_tools_list.js
+++ b/mod/lti/amd/src/course_tools_list.js
@@ -27,8 +27,7 @@ import Notification from 'core/notification';
import Pending from 'core/pending';
import Ajax from 'core/ajax';
import {add as addToast} from 'core/toast';
-import {prefetchStrings} from 'core/prefetch';
-import {get_string as getString} from 'core/str';
+import {get_string as getString, get_strings as getStrings} from 'core/str';
import {refreshTableContent} from 'core_table/dynamic';
import * as Selectors from 'core_table/local/dynamic/selectors';
@@ -36,30 +35,31 @@ import * as Selectors from 'core_table/local/dynamic/selectors';
* Initialise module.
*/
export const init = () => {
- prefetchStrings('mod_lti', [
- 'deletecoursetool',
- 'deletecoursetoolconfirm',
- 'coursetooldeleted'
- ]);
-
- prefetchStrings('core', [
- 'delete',
- ]);
-
document.addEventListener('click', event => {
const courseToolDelete = event.target.closest('[data-action="course-tool-delete"]');
if (courseToolDelete) {
event.preventDefault();
+ // A different message is used in the modal if the tool has usages within the course.
+ const usage = courseToolDelete.dataset.courseToolUsage;
+ const deleteBodyStringId = usage > 0 ? 'deletecoursetoolwithusageconfirm' : 'deletecoursetoolconfirm';
+ const requiredStrings = [
+ {key: 'deletecoursetool', component: 'mod_lti', param: courseToolDelete.dataset.courseToolName},
+ {key: deleteBodyStringId, component: 'mod_lti', param: courseToolDelete.dataset.courseToolName},
+ {key: 'delete', component: 'core', param: courseToolDelete.dataset.courseToolName},
+ {key: 'coursetooldeleted', component: 'mod_lti', param: courseToolDelete.dataset.courseToolName}
+ ];
// Use triggerElement to return focus to the action menu toggle.
const triggerElement = courseToolDelete.closest('.dropdown').querySelector('.dropdown-toggle');
- Notification.saveCancelPromise(
- getString('deletecoursetool', 'mod_lti'),
- getString('deletecoursetoolconfirm', 'mod_lti', courseToolDelete.dataset.courseToolName),
- getString('delete', 'core'),
- {triggerElement}
- ).then(() => {
+
+ getStrings(requiredStrings).then(([modalTitle, modalBody, deleteLabel]) => {
+ return Notification.deleteCancelPromise(
+ modalTitle,
+ modalBody,
+ deleteLabel,
+ {triggerElement});
+ }).then(() => {
const pendingPromise = new Pending('mod_lti/course_tools:delete');
const request = {
@@ -67,7 +67,7 @@ export const init = () => {
args: {tooltypeid: courseToolDelete.dataset.courseToolId}
};
return Ajax.call([request])[0]
- .then(addToast(getString('coursetooldeleted', 'mod_lti')))
+ .then(addToast(getString('coursetooldeleted', 'mod_lti', courseToolDelete.dataset.courseToolName)))
.then(() => {
const tableRoot = triggerElement.closest(Selectors.main.region);
return refreshTableContent(tableRoot);
diff --git a/mod/lti/classes/reportbuilder/local/systemreports/course_external_tools_list.php b/mod/lti/classes/reportbuilder/local/systemreports/course_external_tools_list.php
index 01ff5baa114..fad07005b3a 100644
--- a/mod/lti/classes/reportbuilder/local/systemreports/course_external_tools_list.php
+++ b/mod/lti/classes/reportbuilder/local/systemreports/course_external_tools_list.php
@@ -33,6 +33,9 @@ class course_external_tools_list extends system_report {
/** @var \stdClass the course to constrain the report to. */
protected \stdClass $course;
+ /** @var int the usage count for the tool represented in a row, and set by row_callback(). */
+ protected int $perrowtoolusage = 0;
+
/**
* Initialise report, we need to set the main table, load our entities and set columns/filters
*/
@@ -54,8 +57,13 @@ class course_external_tools_list extends system_report {
$this->add_filters();
$this->add_actions();
- // We need id and course in the actions, without entity prefixes, so add these here.
- $this->add_base_fields("{$entitymainalias}.id, {$entitymainalias}.course");
+ // We need id and course in the actions column, without entity prefixes, so add these here.
+ // We also need access to the tool usage count in a few places (the usage column as well as the actions column).
+ $ti = database::generate_param_name(); // Tool instance param.
+ $this->add_base_fields("{$entitymainalias}.id, {$entitymainalias}.course, ".
+ "(SELECT COUNT($ti.id)
+ FROM {lti} $ti
+ WHERE $ti.typeid = {$entitymainalias}.id) AS toolusage");
// Scope the report to the course context only.
$paramprefix = database::generate_param_name();
@@ -79,6 +87,10 @@ class course_external_tools_list extends system_report {
return has_capability('mod/lti:addpreconfiguredinstance', $this->get_context());
}
+ public function row_callback(\stdClass $row): void {
+ $this->perrowtoolusage = $row->toolusage;
+ }
+
/**
* Adds the columns we want to display in the report.
*
@@ -97,12 +109,8 @@ class course_external_tools_list extends system_report {
$this->add_columns_from_entities($columns);
- // Tool usage column using a custom SQL subquery to count tool instances within the course.
+ // Tool usage column using a custom SQL subquery (defined in initialise method) to count tool instances within the course.
// TODO: This should be replaced with proper column aggregation once that's added to system_report instances in MDL-76392.
- $ti = database::generate_param_name(); // Tool instance param.
- $sql = "(SELECT COUNT($ti.id)
- FROM {lti} $ti
- WHERE $ti.typeid = {$entitymainalias}.id)";
$this->add_column(new column(
'usage',
new \lang_string('usage', 'mod_lti'),
@@ -110,7 +118,8 @@ class course_external_tools_list extends system_report {
))
->set_type(column::TYPE_INTEGER)
->set_is_sortable(true)
- ->add_field($sql, 'usage');
+ ->add_field("{$entitymainalias}.id")
+ ->add_callback(fn() => $this->perrowtoolusage);
// Attempt to create a dummy actions column, working around the limitations of the official actions feature.
$this->add_column(new column(
@@ -120,7 +129,7 @@ class course_external_tools_list extends system_report {
->set_type(column::TYPE_TEXT)
->set_is_sortable(false)
->add_fields("{$entitymainalias}.id, {$entitymainalias}.course, {$entitymainalias}.name")
- ->add_callback(static function($field, $row) {
+ ->add_callback(function($field, $row) {
global $OUTPUT;
// Lock actions for site-level preconfigured tools.
@@ -161,7 +170,8 @@ class course_external_tools_list extends system_report {
[
'data-action' => 'course-tool-delete',
'data-course-tool-id' => $row->id,
- 'data-course-tool-name' => $row->name
+ 'data-course-tool-name' => $row->name,
+ 'data-course-tool-usage' => $this->perrowtoolusage
],
));
diff --git a/mod/lti/lang/en/lti.php b/mod/lti/lang/en/lti.php
index 4231326123d..093c035f336 100644
--- a/mod/lti/lang/en/lti.php
+++ b/mod/lti/lang/en/lti.php
@@ -130,7 +130,7 @@ $string['courseid'] = 'Course ID number';
$string['courseinformation'] = 'Course information';
$string['courselink'] = 'Go to course';
$string['coursemisconf'] = 'Course is misconfigured';
-$string['coursetooldeleted'] = 'Course tool deleted';
+$string['coursetooldeleted'] = '{$a} removed';
$string['createdon'] = 'Created on';
$string['curllibrarymissing'] = 'PHP cURL extension required for the External tool.';
$string['custom'] = 'Custom parameters';
@@ -159,8 +159,9 @@ $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['deletecoursetool'] = 'Delete a course tool';
-$string['deletecoursetoolconfirm'] = 'Are you sure you want to delete this course tool?';
+$string['deletecoursetool'] = 'Delete {$a}';
+$string['deletecoursetoolconfirm'] = 'This will delete {$a} from the available LTI tools in your course.';
+$string['deletecoursetoolwithusageconfirm'] = '{$a} is currently being used in at least one activity in your course. If you delete this tool, the activities that use it will no longer work.
Are you sure you want to delete {$a}?';
$string['deletetype'] = 'Delete preconfigured tool';
$string['display_description'] = 'Display activity description when launched';
$string['display_description_help'] = 'If selected, the activity description (specified above) will display above the tool provider\'s content.
@@ -568,6 +569,7 @@ back to the \'Configured\' category so the registration process can be restarted
$string['toolproxyregistration'] = 'External tool registration';
$string['toolregistration'] = 'External tool registration';
$string['toolsetup'] = 'External tool configuration';
+$string['tooltypenotfounderror'] = "The LTI tool used in this activity has been deleted. If you need help, contact your teacher or site administrator.";
$string['tooltypes'] = 'Tools';
$string['tooltypeadded'] = 'Preconfigured tool added';
$string['tooltypedeleted'] = 'Preconfigured tool deleted';
diff --git a/mod/lti/launch.php b/mod/lti/launch.php
index 5b55e9e70f6..db3b97f61e5 100644
--- a/mod/lti/launch.php
+++ b/mod/lti/launch.php
@@ -63,17 +63,21 @@ if (empty($typeid) && ($tool = lti_get_tool_by_url_match($lti->toolurl))) {
$typeid = $tool->id;
}
if ($typeid) {
- $config = lti_get_type_type_config($typeid);
- if ($config->lti_ltiversion === LTI_VERSION_1P3) {
- if (!isset($SESSION->lti_initiatelogin_status)) {
- $msgtype = 'basic-lti-launch-request';
- if ($action === 'gradeReport') {
- $msgtype = 'LtiSubmissionReviewRequest';
+ $config = lti_get_type_config($typeid);
+ $missingtooltype = empty($config);
+ if (!$missingtooltype) {
+ $config = lti_get_type_type_config($typeid);
+ if ($config->lti_ltiversion === LTI_VERSION_1P3) {
+ if (!isset($SESSION->lti_initiatelogin_status)) {
+ $msgtype = 'basic-lti-launch-request';
+ if ($action === 'gradeReport') {
+ $msgtype = 'LtiSubmissionReviewRequest';
+ }
+ echo lti_initiate_login($cm->course, $cmid, $lti, $config, $msgtype, '', '', $foruserid);
+ exit;
+ } else {
+ unset($SESSION->lti_initiatelogin_status);
}
- echo lti_initiate_login($cm->course, $cmid, $lti, $config, $msgtype, '', '', $foruserid);
- exit;
- } else {
- unset($SESSION->lti_initiatelogin_status);
}
}
}
@@ -85,6 +89,15 @@ $context = context_module::instance($cm->id);
require_login($course, true, $cm);
require_capability('mod/lti:view', $context);
+if (!empty($missingtooltype)) {
+ $PAGE->set_url(new moodle_url('/mod/lti/launch.php'));
+ $PAGE->set_context($context);
+ $PAGE->set_secondary_active_tab('modulepage');
+ $PAGE->set_pagelayout('incourse');
+ echo $OUTPUT->header();
+ throw new moodle_exception('tooltypenotfounderror', 'mod_lti');
+}
+
// Completion and trigger events.
if ($triggerview) {
lti_view($lti, $course, $cm, $context);
diff --git a/mod/lti/tests/behat/managecoursetools.feature b/mod/lti/tests/behat/managecoursetools.feature
index 8276b7d9b59..572047c33b8 100644
--- a/mod/lti/tests/behat/managecoursetools.feature
+++ b/mod/lti/tests/behat/managecoursetools.feature
@@ -119,12 +119,12 @@ Feature: Manage course tools
And I navigate to "LTI External tools" in current page administration
When I open the action menu in "Test tool" "table_row"
And I choose "Delete" in the open action menu
- Then I should see "Are you sure you want to delete this course tool?"
- And I click on "Cancel" "button" in the "Delete a course tool" "dialogue"
+ Then I should see "This will delete Test tool from the available LTI tools in your course."
+ And I click on "Cancel" "button" in the "Delete Test tool" "dialogue"
And I should see "Test tool" in the "reportbuilder-table" "table"
And I open the action menu in "Test tool" "table_row"
And I choose "Delete" in the open action menu
- And I should see "Are you sure you want to delete this course tool?"
- And I click on "Delete" "button" in the "Delete a course tool" "dialogue"
- And I should see "Course tool deleted"
+ And I should see "This will delete Test tool from the available LTI tools in your course."
+ And I click on "Delete" "button" in the "Delete Test tool" "dialogue"
+ And I should see "Test tool removed"
And I should not see "Test tool" in the "reportbuilder-table" "table"
diff --git a/mod/lti/view.php b/mod/lti/view.php
index 7d7e942183f..47066989459 100644
--- a/mod/lti/view.php
+++ b/mod/lti/view.php
@@ -74,7 +74,10 @@ if (empty($typeid) && ($tool = lti_get_tool_by_url_match($lti->toolurl))) {
}
if ($typeid) {
$toolconfig = lti_get_type_config($typeid);
- $toolurl = $toolconfig['toolurl'];
+ $missingtooltype = empty($toolconfig);
+ if (!$missingtooltype) {
+ $toolurl = $toolconfig['toolurl'];
+ }
} else {
$toolconfig = array();
$toolurl = $lti->toolurl;
@@ -94,6 +97,13 @@ if (!empty($foruserid) && (int)$foruserid !== (int)$USER->id) {
$url = new moodle_url('/mod/lti/view.php', array('id' => $cm->id));
$PAGE->set_url($url);
+
+if (!empty($missingtooltype)) {
+ $PAGE->set_pagelayout('incourse');
+ echo $OUTPUT->header();
+ throw new moodle_exception('tooltypenotfounderror', 'mod_lti');
+}
+
$launchcontainer = lti_get_launch_container($lti, $toolconfig);
if ($launchcontainer == LTI_LAUNCH_CONTAINER_EMBED_NO_BLOCKS) {