Merge branch 'MDL-78576-master' of https://github.com/snake/moodle

This commit is contained in:
Andrew Nicols 2023-08-16 23:16:18 +08:00 committed by Sara Arjona
commit edf29562cc
No known key found for this signature in database
8 changed files with 85 additions and 50 deletions

View File

@ -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

File diff suppressed because one or more lines are too long

View File

@ -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);

View File

@ -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
],
));

View File

@ -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.<br><br>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';

View File

@ -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);

View File

@ -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"

View File

@ -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) {