diff --git a/mod/lti/amd/build/contentitem.min.js b/mod/lti/amd/build/contentitem.min.js
index e54c2cab9bb..607b51838fe 100644
--- a/mod/lti/amd/build/contentitem.min.js
+++ b/mod/lti/amd/build/contentitem.min.js
@@ -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("cmidnumber",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)}}});
+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)}}});
//# sourceMappingURL=contentitem.min.js.map
diff --git a/mod/lti/amd/build/contentitem.min.js.map b/mod/lti/amd/build/contentitem.min.js.map
index 69fb8147107..32c354f6600 100644
--- a/mod/lti/amd/build/contentitem.min.js.map
+++ b/mod/lti/amd/build/contentitem.min.js.map
@@ -1 +1 @@
-{"version":3,"sources":["../src/contentitem.js"],"names":["define","$","notification","str","templates","FormField","ModalFactory","ModalEvents","dialogue","doneCallback","ltiFormFields","TYPES","TEXT","EDITOR","CHECKBOX","SELECT","window","processContentItemReturnData","returnData","hide","index","field","value","name","setFieldValue","init","url","postData","cb","bodyPromise","render","setBody","show","get_string","then","title","create","body","large","modal","getRoot","on","hidden","fetchNotifications","catch","exception"],"mappings":"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","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 * @class contentitem\n * @package mod_lti\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_factory',\n 'core/modal_events'\n ],\n function($, notification, str, templates, FormField, ModalFactory, 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 ModalFactory.create({\n title: title,\n body: bodyPromise,\n large: 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\n // Display the dialogue.\n modal.show();\n return;\n }).catch(notification.exception);\n }\n };\n\n /**\n * Array of form fields for LTI tool configuration.\n *\n * @type {*[]}\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('cmidnumber', FormField.TYPES.TEXT, true, '')\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 *\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\n // Populate LTI configuration fields from return data.\n var index;\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\n if (doneCallback) {\n doneCallback();\n }\n };\n\n return contentItem;\n }\n);\n"],"file":"contentitem.min.js"}
\ No newline at end of file
+{"version":3,"sources":["../src/contentitem.js"],"names":["define","$","notification","str","templates","FormField","ModalFactory","ModalEvents","dialogue","doneCallback","ltiFormFields","TYPES","TEXT","EDITOR","CHECKBOX","SELECT","window","processContentItemReturnData","returnData","hide","index","field","value","name","setFieldValue","init","url","postData","cb","bodyPromise","render","setBody","show","get_string","then","title","create","body","large","modal","getRoot","on","hidden","fetchNotifications","catch","exception"],"mappings":"AA4BAA,OAAM,uBACF,CACI,QADJ,CAEI,mBAFJ,CAGI,UAHJ,CAII,gBAJJ,CAKI,oBALJ,CAMI,oBANJ,CAOI,mBAPJ,CADE,CAUF,SAASC,CAAT,CAAYC,CAAZ,CAA0BC,CAA1B,CAA+BC,CAA/B,CAA0CC,CAA1C,CAAqDC,CAArD,CAAmEC,CAAnE,CAAgF,IACxEC,CAAAA,CADwE,CAExEC,CAFwE,CAwDxEC,CAAa,CAAG,CAChB,GAAIL,CAAAA,CAAJ,CAAc,MAAd,CAAsBA,CAAS,CAACM,KAAV,CAAgBC,IAAtC,IAAmD,EAAnD,CADgB,CAEhB,GAAIP,CAAAA,CAAJ,CAAc,aAAd,CAA6BA,CAAS,CAACM,KAAV,CAAgBE,MAA7C,IAA4D,EAA5D,CAFgB,CAGhB,GAAIR,CAAAA,CAAJ,CAAc,SAAd,CAAyBA,CAAS,CAACM,KAAV,CAAgBC,IAAzC,IAAqD,EAArD,CAHgB,CAIhB,GAAIP,CAAAA,CAAJ,CAAc,eAAd,CAA+BA,CAAS,CAACM,KAAV,CAAgBC,IAA/C,IAA2D,EAA3D,CAJgB,CAKhB,GAAIP,CAAAA,CAAJ,CAAc,8BAAd,CAA8CA,CAAS,CAACM,KAAV,CAAgBG,QAA9D,OALgB,CAMhB,GAAIT,CAAAA,CAAJ,CAAc,0BAAd,CAA0CA,CAAS,CAACM,KAAV,CAAgBG,QAA1D,OANgB,CAOhB,GAAIT,CAAAA,CAAJ,CAAc,+BAAd,CAA+CA,CAAS,CAACM,KAAV,CAAgBG,QAA/D,OAPgB,CAQhB,GAAIT,CAAAA,CAAJ,CAAc,4BAAd,CAA4CA,CAAS,CAACM,KAAV,CAAgBC,IAA5D,IAAwE,EAAxE,CARgB,CAShB,GAAIP,CAAAA,CAAJ,CAAc,MAAd,CAAsBA,CAAS,CAACM,KAAV,CAAgBC,IAAtC,IAAkD,EAAlD,CATgB,CAUhB,GAAIP,CAAAA,CAAJ,CAAc,YAAd,CAA4BA,CAAS,CAACM,KAAV,CAAgBC,IAA5C,IAAwD,EAAxD,CAVgB,CAWhB,GAAIP,CAAAA,CAAJ,CAAc,iBAAd,CAAiCA,CAAS,CAACM,KAAV,CAAgBI,MAAjD,IAA+D,CAA/D,CAXgB,CAYhB,GAAIV,CAAAA,CAAJ,CAAc,sBAAd,CAAsCA,CAAS,CAACM,KAAV,CAAgBC,IAAtD,IAAmE,EAAnE,CAZgB,CAahB,GAAIP,CAAAA,CAAJ,CAAc,oBAAd,CAAoCA,CAAS,CAACM,KAAV,CAAgBC,IAApD,IAAgE,EAAhE,CAbgB,CAchB,GAAIP,CAAAA,CAAJ,CAAc,aAAd,CAA6BA,CAAS,CAACM,KAAV,CAAgBC,IAA7C,IAAyD,EAAzD,CAdgB,CAxDwD,CA8E5EI,MAAM,CAACC,4BAAP,CAAsC,SAASC,CAAT,CAAqB,CACvD,GAAIV,CAAJ,CAAc,CACVA,CAAQ,CAACW,IAAT,EACH,CAID,OAAKC,CAAAA,CAAL,GAAcV,CAAAA,CAAd,CAA6B,IACrBW,CAAAA,CAAK,CAAGX,CAAa,CAACU,CAAD,CADA,CAErBE,CAAK,CAAG,IAFa,CAGzB,GAAsC,WAAlC,QAAOJ,CAAAA,CAAU,CAACG,CAAK,CAACE,IAAP,CAArB,CAAmD,CAC/CD,CAAK,CAAGJ,CAAU,CAACG,CAAK,CAACE,IAAP,CACrB,CACDF,CAAK,CAACG,aAAN,CAAoBF,CAApB,CACH,CAED,GAAIb,CAAJ,CAAkB,CACdA,CAAY,EACf,CACJ,CAnBD,CAqBA,MAhGkB,CAQdgB,IAAI,CAAE,cAASC,CAAT,CAAcC,CAAd,CAAwBC,CAAxB,CAA4B,CAC9BnB,CAAY,CAAGmB,CAAf,CAD8B,GAM1BC,CAAAA,CAAW,CAAGzB,CAAS,CAAC0B,MAAV,CAAiB,qBAAjB,CAJJ,CACVJ,GAAG,CAAEA,CADK,CAEVC,QAAQ,CAAEA,CAFA,CAII,CANY,CAQ9B,GAAInB,CAAJ,CAAc,CAEVA,CAAQ,CAACuB,OAAT,CAAiBF,CAAjB,EAEArB,CAAQ,CAACwB,IAAT,GACA,MACH,CAED7B,CAAG,CAAC8B,UAAJ,CAAe,eAAf,CAAgC,KAAhC,EAAuCC,IAAvC,CAA4C,SAASC,CAAT,CAAgB,CACxD,MAAO7B,CAAAA,CAAY,CAAC8B,MAAb,CAAoB,CACvBD,KAAK,CAAEA,CADgB,CAEvBE,IAAI,CAAER,CAFiB,CAGvBS,KAAK,GAHkB,CAApB,CAKV,CAND,EAMGJ,IANH,CAMQ,SAASK,CAAT,CAAgB,CACpB/B,CAAQ,CAAG+B,CAAX,CAEAA,CAAK,CAACC,OAAN,GAAgBC,EAAhB,CAAmBlC,CAAW,CAACmC,MAA/B,CAAuC,UAAW,CAE9CH,CAAK,CAACR,OAAN,CAAc,EAAd,EAGA7B,CAAY,CAACyC,kBAAb,EACH,CAND,EASAJ,CAAK,CAACP,IAAN,EAEH,CApBD,EAoBGY,KApBH,CAoBS1C,CAAY,CAAC2C,SApBtB,CAqBH,CA7Ca,CAiGrB,CA9GC,CAAN","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 * @class contentitem\n * @package mod_lti\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_factory',\n 'core/modal_events'\n ],\n function($, notification, str, templates, FormField, ModalFactory, 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 ModalFactory.create({\n title: title,\n body: bodyPromise,\n large: 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\n // Display the dialogue.\n modal.show();\n return;\n }).catch(notification.exception);\n }\n };\n\n /**\n * Array of form fields for LTI tool configuration.\n *\n * @type {*[]}\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 ];\n\n /**\n * Window function that can be called from mod_lti/contentitem_return to close the dialogue and process the return data.\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\n // Populate LTI configuration fields from return data.\n var index;\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\n if (doneCallback) {\n doneCallback();\n }\n };\n\n return contentItem;\n }\n);\n"],"file":"contentitem.min.js"}
\ No newline at end of file
diff --git a/mod/lti/amd/src/contentitem.js b/mod/lti/amd/src/contentitem.js
index ee9106a27dd..bc18ae229db 100644
--- a/mod/lti/amd/src/contentitem.js
+++ b/mod/lti/amd/src/contentitem.js
@@ -105,7 +105,8 @@ define(
new FormField('secureicon', FormField.TYPES.TEXT, true, ''),
new FormField('launchcontainer', FormField.TYPES.SELECT, true, 0),
new FormField('grade_modgrade_point', FormField.TYPES.TEXT, false, ''),
- new FormField('cmidnumber', FormField.TYPES.TEXT, true, '')
+ new FormField('lineitemresourceid', FormField.TYPES.TEXT, true, ''),
+ new FormField('lineitemtag', FormField.TYPES.TEXT, true, '')
];
/**
diff --git a/mod/lti/lib.php b/mod/lti/lib.php
index 833be563be8..107cd39b7f3 100644
--- a/mod/lti/lib.php
+++ b/mod/lti/lib.php
@@ -47,6 +47,7 @@
*/
defined('MOODLE_INTERNAL') || die;
+use ltiservice_gradebookservices\local\service\gradebookservices;
/**
* List of features supported in URL module
@@ -118,6 +119,8 @@ function lti_add_instance($lti, $mform) {
lti_grade_item_update($lti);
}
+ gradebookservices::update_coupled_gradebookservices($lti, $lti->lineitemresourceid ?? '', $lti->lineitemtag ?? '');
+
$completiontimeexpected = !empty($lti->completionexpected) ? $lti->completionexpected : null;
\core_completion\api::update_completion_date_event($lti->coursemodule, 'lti', $lti->id, $completiontimeexpected);
@@ -165,6 +168,8 @@ function lti_update_instance($lti, $mform) {
$lti->typeid = $lti->urlmatchedtypeid;
}
+ gradebookservices::update_coupled_gradebookservices($lti, $lti->lineitemresourceid, $lti->lineitemtag);
+
$completiontimeexpected = !empty($lti->completionexpected) ? $lti->completionexpected : null;
\core_completion\api::update_completion_date_event($lti->coursemodule, 'lti', $lti->id, $completiontimeexpected);
diff --git a/mod/lti/locallib.php b/mod/lti/locallib.php
index da61f6eacf3..37529326718 100644
--- a/mod/lti/locallib.php
+++ b/mod/lti/locallib.php
@@ -1515,8 +1515,13 @@ function lti_tool_configuration_from_content_item($typeid, $messagetype, $ltiver
}
}
$config->grade_modgrade_point = $maxscore;
+ $config->lineitemresourceid = '';
+ $config->lineitemtag = '';
if (isset($lineitem->assignedActivity) && isset($lineitem->assignedActivity->activityId)) {
- $config->cmidnumber = $lineitem->assignedActivity->activityId;
+ $config->lineitemresourceid = $lineitem->assignedActivity->activityId ? : '';
+ }
+ if (isset($lineitem->tag)) {
+ $config->lineitemtag = $lineitem->tag ? : '';
}
}
}
@@ -1613,6 +1618,9 @@ function lti_convert_content_items($param) {
$newitem->lineItem->assignedActivity = new stdClass();
$newitem->lineItem->assignedActivity->activityId = $item->lineItem->resourceId;
}
+ if (isset($item->lineItem->tag)) {
+ $newitem->lineItem->tag = $item->lineItem->tag;
+ }
if (isset($item->lineItem->scoreMaximum)) {
$newitem->lineItem->scoreConstraints = new stdClass();
$newitem->lineItem->scoreConstraints->{'@type'} = 'NumericLimits';
diff --git a/mod/lti/mod_form.php b/mod/lti/mod_form.php
index 8eb9fac73f9..b79e889e624 100644
--- a/mod/lti/mod_form.php
+++ b/mod/lti/mod_form.php
@@ -50,6 +50,7 @@ defined('MOODLE_INTERNAL') || die;
require_once($CFG->dirroot.'/course/moodleform_mod.php');
require_once($CFG->dirroot.'/mod/lti/locallib.php');
+use ltiservice_gradebookservices\local\service\gradebookservices;
class mod_lti_mod_form extends moodleform_mod {
@@ -207,6 +208,12 @@ class mod_lti_mod_form extends moodleform_mod {
$mform->addElement('hidden', 'urlmatchedtypeid', '', array( 'id' => 'id_urlmatchedtypeid' ));
$mform->setType('urlmatchedtypeid', PARAM_INT);
+ $mform->addElement('hidden', 'lineitemresourceid', '', array( 'id' => 'id_lineitemresourceid' ));
+ $mform->setType('lineitemresourceid', PARAM_TEXT);
+
+ $mform->addElement('hidden', 'lineitemtag', '', array( 'id' => 'id_lineitemtag'));
+ $mform->setType('lineitemtag', PARAM_TEXT);
+
$launchoptions = array();
$launchoptions[LTI_LAUNCH_CONTAINER_DEFAULT] = get_string('default', 'lti');
$launchoptions[LTI_LAUNCH_CONTAINER_EMBED] = get_string('embed', 'lti');
@@ -331,4 +338,22 @@ class mod_lti_mod_form extends moodleform_mod {
$PAGE->requires->js_init_call('M.mod_lti.editor.init', array(json_encode($jsinfo)), true, $module);
}
+ /**
+ * Sets the current values for resource and tag in case of update.
+ *
+ * @param object $defaultvalues default values to populate the form with.
+ */
+ public function set_data($defaultvalues) {
+ $defaultvalues->lineitemresourceid = '';
+ $defaultvalues->lineitemtag = '';
+ if (is_object($defaultvalues) && $defaultvalues->instance) {
+ $gbs = gradebookservices::find_ltiservice_gradebookservice_for_lti($defaultvalues->instance);
+ if ($gbs) {
+ $defaultvalues->lineitemresourceid = $gbs->resourceid;
+ $defaultvalues->lineitemtag = $gbs->tag;
+ }
+ }
+
+ parent::set_data($defaultvalues);
+ }
}
diff --git a/mod/lti/service/gradebookservices/backup/moodle2/backup_ltiservice_gradebookservices_subplugin.class.php b/mod/lti/service/gradebookservices/backup/moodle2/backup_ltiservice_gradebookservices_subplugin.class.php
index b53168f84e0..27273747860 100644
--- a/mod/lti/service/gradebookservices/backup/moodle2/backup_ltiservice_gradebookservices_subplugin.class.php
+++ b/mod/lti/service/gradebookservices/backup/moodle2/backup_ltiservice_gradebookservices_subplugin.class.php
@@ -61,6 +61,7 @@ class backup_ltiservice_gradebookservices_subplugin extends backup_subplugin {
'typeid',
'baseurl',
'ltilinkid',
+ 'resourceid',
'tag',
'vendorcode',
'guid'
diff --git a/mod/lti/service/gradebookservices/backup/moodle2/restore_ltiservice_gradebookservices_subplugin.class.php b/mod/lti/service/gradebookservices/backup/moodle2/restore_ltiservice_gradebookservices_subplugin.class.php
index 35a16f60415..f4125ae97e4 100644
--- a/mod/lti/service/gradebookservices/backup/moodle2/restore_ltiservice_gradebookservices_subplugin.class.php
+++ b/mod/lti/service/gradebookservices/backup/moodle2/restore_ltiservice_gradebookservices_subplugin.class.php
@@ -97,6 +97,10 @@ class restore_ltiservice_gradebookservices_subplugin extends restore_subplugin {
} else {
$ltilinkid = null;
}
+ $resourceid = null;
+ if (property_exists( $data, 'resourceid' )) {
+ $resourceid = $data->resourceid;
+ }
// If this has not been restored before.
if ($this->get_mappingid('gbsgradeitemrestored', $data->id, 0) == 0) {
$newgbsid = $DB->insert_record('ltiservice_gradebookservices', (object) array(
@@ -106,6 +110,7 @@ class restore_ltiservice_gradebookservices_subplugin extends restore_subplugin {
'ltilinkid' => $ltilinkid,
'typeid' => $newtypeid,
'baseurl' => $data->baseurl,
+ 'resourceid' => $resourceid,
'tag' => $data->tag
));
$this->set_mapping('gbsgradeitemoldid', $newgbsid, $data->gradeitemid);
diff --git a/mod/lti/service/gradebookservices/classes/local/resources/lineitem.php b/mod/lti/service/gradebookservices/classes/local/resources/lineitem.php
index a7131733718..85958532437 100644
--- a/mod/lti/service/gradebookservices/classes/local/resources/lineitem.php
+++ b/mod/lti/service/gradebookservices/classes/local/resources/lineitem.php
@@ -170,17 +170,14 @@ class lineitem extends resource_base {
}
$item->grademax = grade_floatval($json->scoreMaximum);
}
- $resourceid = (isset($json->resourceId)) ? $json->resourceId : '';
- if ($item->idnumber !== $resourceid) {
- $updategradeitem = true;
- }
- $item->idnumber = $resourceid;
if ($gbs) {
- $tag = (isset($json->tag)) ? $json->tag : null;
- if ($gbs->tag !== $tag) {
+ $resourceid = (isset($json->resourceId)) ? $json->resourceId : '';
+ $tag = (isset($json->tag)) ? $json->tag : '';
+ if ($gbs->tag !== $tag || $gbs->resourceid !== $resourceid) {
$upgradegradebookservices = true;
}
$gbs->tag = $tag;
+ $gbs->resourceid = $resourceid;
}
$ltilinkid = null;
if (isset($json->resourceLinkId)) {
@@ -259,6 +256,7 @@ class lineitem extends resource_base {
'typeid' => $typeid,
'baseurl' => $baseurl,
'ltilinkid' => $ltilinkid,
+ 'resourceid' => $resourceid,
'tag' => $gbs->tag
));
}
diff --git a/mod/lti/service/gradebookservices/classes/local/resources/lineitems.php b/mod/lti/service/gradebookservices/classes/local/resources/lineitems.php
index 2efc688bcc0..86deee92a74 100644
--- a/mod/lti/service/gradebookservices/classes/local/resources/lineitems.php
+++ b/mod/lti/service/gradebookservices/classes/local/resources/lineitems.php
@@ -266,33 +266,23 @@ class lineitems extends resource_base {
$toolproxyid = null;
$baseurl = lti_get_type_type_config($typeid)->lti_toolurl;
}
- $params = array();
- $params['itemname'] = $json->label;
- $params['gradetype'] = GRADE_TYPE_VALUE;
- $params['grademax'] = $max;
- $params['grademin'] = 0;
- $item = new \grade_item(array('id' => 0, 'courseid' => $contextid));
- \grade_item::set_properties($item, $params);
- $item->itemtype = 'manual';
- $item->idnumber = $resourceid;
- $item->grademax = $max;
- $id = $item->insert('mod/ltiservice_gradebookservices');
- $DB->insert_record('ltiservice_gradebookservices', (object)array(
- 'gradeitemid' => $id,
- 'courseid' => $contextid,
- 'toolproxyid' => $toolproxyid,
- 'typeid' => $typeid,
- 'baseurl' => $baseurl,
- 'ltilinkid' => $ltilinkid,
- 'tag' => $tag
- ));
+ $gradebookservices = new gradebookservices();
+ $id = $gradebookservices->add_standalone_lineitem($contextid,
+ $json->label,
+ $max,
+ $baseurl,
+ $ltilinkid,
+ $resourceid,
+ $tag,
+ $typeid,
+ $toolproxyid);
+
if (is_null($typeid)) {
$json->id = parent::get_endpoint() . "/{$id}/lineitem";
} else {
$json->id = parent::get_endpoint() . "/{$id}/lineitem?type_id={$typeid}";
}
return json_encode($json, JSON_UNESCAPED_SLASHES);
-
}
/**
diff --git a/mod/lti/service/gradebookservices/classes/local/service/gradebookservices.php b/mod/lti/service/gradebookservices/classes/local/service/gradebookservices.php
index c73df89a523..136278b1d98 100644
--- a/mod/lti/service/gradebookservices/classes/local/service/gradebookservices.php
+++ b/mod/lti/service/gradebookservices/classes/local/service/gradebookservices.php
@@ -158,25 +158,25 @@ class gradebookservices extends service_base {
$this->get_typeconfig()['ltiservice_gradesynchronization'] == self::GRADEBOOKSERVICES_FULL) {
// Check for used in context is only needed because there is no explicit site tool - course relation.
if ($this->is_allowed_in_context($typeid, $courseid)) {
- if (is_null($modlti)) {
- $id = null;
- } else {
+ $id = null;
+ if (!is_null($modlti)) {
$conditions = array('courseid' => $courseid, 'itemtype' => 'mod',
'itemmodule' => 'lti', 'iteminstance' => $modlti);
- $lineitems = $DB->get_records('grade_items', $conditions);
+ $coupledlineitems = $DB->get_records('grade_items', $conditions);
$conditionsgbs = array('courseid' => $courseid, 'ltilinkid' => $modlti);
$lineitemsgbs = $DB->get_records('ltiservice_gradebookservices', $conditionsgbs);
- if (count($lineitems) + count($lineitemsgbs) == 1) {
- if ($lineitems) {
- $lineitem = reset($lineitems);
- $id = $lineitem->id;
+ // If a link has more that one attached grade items, per spec we do not populate line item url.
+ if (count($lineitemsgbs) == 1) {
+ $id = reset($lineitemsgbs)->gradeitemid;
+ }
+ if (count($lineitemsgbs) < 2 && count($coupledlineitems) == 1) {
+ $coupledid = reset($coupledlineitems)->id;
+ if (!is_null($id) && $id != $coupledid) {
+ $id = null;
} else {
- $lineitemsgb = reset($lineitemsgbs);
- $id = $lineitemsgb->gradeitemid;
+ $id = $coupledid;
}
- } else {
- $id = null;
}
}
$launchparameters['gradebookservices_scope'] = implode(',', $this->get_permitted_scopes());
@@ -210,15 +210,9 @@ class gradebookservices extends service_base {
// Select all lti potential linetiems in site.
$params = array('courseid' => $courseid);
- $optionalfilters = "";
- if (isset($resourceid)) {
- $optionalfilters .= " AND (i.idnumber = :resourceid)";
- $params['resourceid'] = $resourceid;
- }
$sql = "SELECT i.*
FROM {grade_items} i
WHERE (i.courseid = :courseid)
- {$optionalfilters}
ORDER BY i.id";
$lineitems = $DB->get_records_sql($sql, $params);
@@ -230,7 +224,8 @@ class gradebookservices extends service_base {
foreach ($lineitems as $lineitem) {
$gbs = $this->find_ltiservice_gradebookservice_for_lineitem($lineitem->id);
if ($gbs && (!isset($tag) || (isset($tag) && $gbs->tag == $tag))
- && (!isset($ltilinkid) || (isset($ltilinkid) && $gbs->ltilinkid == $ltilinkid))) {
+ && (!isset($ltilinkid) || (isset($ltilinkid) && $gbs->ltilinkid == $ltilinkid))
+ && (!isset($resourceid) || (isset($resourceid) && $gbs->resourceid == $resourceid))) {
if (is_null($typeid)) {
if ($this->get_tool_proxy()->id == $gbs->toolproxyid) {
array_push($lineitemstoreturn, $lineitem);
@@ -240,8 +235,12 @@ class gradebookservices extends service_base {
array_push($lineitemstoreturn, $lineitem);
}
}
- } else if (($lineitem->itemtype == 'mod') && ($lineitem->itemmodule == 'lti') && (!isset($tag) &&
- (!isset($ltilinkid) || (isset($ltilinkid) && $lineitem->iteminstance == $ltilinkid)))) {
+ } else if (($lineitem->itemtype == 'mod'
+ && $lineitem->itemmodule == 'lti'
+ && !isset($resourceid)
+ && !isset($tag)
+ && (!isset($ltilinkid) || (isset($ltilinkid)
+ && $lineitem->iteminstance == $ltilinkid)))) {
// We will need to check if the activity related belongs to our tool proxy.
$ltiactivity = $DB->get_record('lti', array('id' => $lineitem->iteminstance));
if (($ltiactivity) && (isset($ltiactivity->typeid))) {
@@ -323,6 +322,59 @@ class gradebookservices extends service_base {
return $lineitem;
}
+ /**
+ * Adds a decoupled (standalone) line item.
+ * Decoupled line items are not directly attached to
+ * an lti instance activity. They are recorded in
+ * the gradebook as manual activities and the
+ * gradebookservices is used to associate that manual column
+ * with the tool in addition to storing the LTI related
+ * metadata (resource id, tag).
+ *
+ * @param string $courseid ID of course
+ * @param string $label label of lineitem
+ * @param float $maximumscore maximum score of lineitem
+ * @param string $baseurl
+ * @param int|null $ltilinkid id of lti instance this line item is associated with
+ * @param string|null $resourceid resource id of lineitem
+ * @param string|null $tag tag of lineitem
+ * @param int $typeid lti type to which this line item is associated with
+ * @param int|null $toolproxyid lti2 tool proxy to which this lineitem is associated to
+ *
+ * @return int id of the created gradeitem
+ */
+ public function add_standalone_lineitem(string $courseid,
+ string $label,
+ float $maximumscore,
+ string $baseurl,
+ ?int $ltilinkid,
+ ?string $resourceid,
+ ?string $tag,
+ int $typeid,
+ int $toolproxyid = null) : int {
+ global $DB;
+ $params = array();
+ $params['itemname'] = $label;
+ $params['gradetype'] = GRADE_TYPE_VALUE;
+ $params['grademax'] = $maximumscore;
+ $params['grademin'] = 0;
+ $item = new \grade_item(array('id' => 0, 'courseid' => $courseid));
+ \grade_item::set_properties($item, $params);
+ $item->itemtype = 'manual';
+ $item->grademax = $maximumscore;
+ $id = $item->insert('mod/ltiservice_gradebookservices');
+ $DB->insert_record('ltiservice_gradebookservices', (object)array(
+ 'gradeitemid' => $id,
+ 'courseid' => $courseid,
+ 'toolproxyid' => $toolproxyid,
+ 'typeid' => $typeid,
+ 'baseurl' => $baseurl,
+ 'ltilinkid' => $ltilinkid,
+ 'resourceid' => $resourceid,
+ 'tag' => $tag
+ ));
+ return $id;
+ }
/**
* Set a grade item.
@@ -341,7 +393,7 @@ class gradebookservices extends service_base {
}
/**
- * Set a grade item.
+ * Saves a score received from the LTI tool.
*
* @param object $gradeitem Grade Item record
* @param object $score Result object
@@ -428,9 +480,9 @@ class gradebookservices extends service_base {
$lineitem->id = "{$endpoint}/{$item->id}/lineitem" . $typeidstring;
$lineitem->label = $item->itemname;
$lineitem->scoreMaximum = floatval($item->grademax);
- $lineitem->resourceId = (!empty($item->idnumber)) ? $item->idnumber : '';
$gbs = self::find_ltiservice_gradebookservice_for_lineitem($item->id);
if ($gbs) {
+ $lineitem->resourceId = (!empty($gbs->resourceid)) ? $gbs->resourceid : '';
$lineitem->tag = (!empty($gbs->tag)) ? $gbs->tag : '';
if (isset($gbs->ltilinkid)) {
$lineitem->resourceLinkId = strval($gbs->ltilinkid);
@@ -561,6 +613,42 @@ class gradebookservices extends service_base {
}
}
+ /**
+ * Updates the tag and resourceid values for a grade item coupled to an lti link instance.
+ *
+ * @param object $ltiinstance The lti instance to which the grade item is coupled to
+ * @param string|null $resourceid The resourceid to apply to the lineitem. Might be an empty string.
+ * @param string|null $tag The tag to apply to the lineitem. Might be an empty string.
+ *
+ */
+ public static function update_coupled_gradebookservices(object $ltiinstance,
+ ?string $resourceid,
+ ?string $tag) : void {
+ global $DB;
+ if ($ltiinstance && $ltiinstance->typeid) {
+ $gradeitem = $DB->get_record('grade_items', array('itemmodule' => 'lti', 'iteminstance' => $ltiinstance->id));
+ if ($gradeitem) {
+ $gbs = self::find_ltiservice_gradebookservice_for_lineitem($gradeitem->id);
+ if ($gbs) {
+ $gbs->resourceid = $resourceid;
+ $gbs->tag = $tag;
+ $DB->update_record('ltiservice_gradebookservices', $gbs);
+ } else {
+ $baseurl = lti_get_type_type_config($ltiinstance->typeid)->lti_toolurl;
+ $DB->insert_record('ltiservice_gradebookservices', (object)array(
+ 'gradeitemid' => $gradeitem->id,
+ 'courseid' => $gradeitem->courseid,
+ 'typeid' => $ltiinstance->typeid,
+ 'baseurl' => $baseurl,
+ 'ltilinkid' => $ltiinstance->id,
+ 'resourceid' => $resourceid,
+ 'tag' => $tag
+ ));
+ }
+ }
+ }
+ }
+
/**
* Deletes orphaned rows from the 'ltiservice_gradebookservices' table.
*
@@ -606,29 +694,34 @@ class gradebookservices extends service_base {
return $gradableuser;
}
+ /**
+ * Find the right element in the ltiservice_gradebookservice table for an lti instance
+ *
+ * @param string $instanceid The LTI module instance id
+ * @return object gradebookservice for this line item
+ */
+ public static function find_ltiservice_gradebookservice_for_lti($instanceid) {
+ global $DB;
+
+ if ($instanceid) {
+ $gradeitem = $DB->get_record('grade_items', array('itemmodule' => 'lti', 'iteminstance' => $instanceid));
+ if ($gradeitem) {
+ return self::find_ltiservice_gradebookservice_for_lineitem($gradeitem->id);
+ }
+ }
+ }
+
/**
* Find the right element in the ltiservice_gradebookservice table for a lineitem
*
- * @param string $lineitemid The lineitem
- * @return object|bool gradebookservice id or false if none
+ * @param string $lineitemid The lineitem (gradeitem) id
+ * @return object gradebookservice if it exists
*/
public static function find_ltiservice_gradebookservice_for_lineitem($lineitemid) {
global $DB;
-
- if (!$lineitemid) {
- return false;
- }
- $gradeitem = $DB->get_record('grade_items', array('id' => $lineitemid));
- if ($gradeitem) {
- $gbs = $DB->get_record('ltiservice_gradebookservices',
- array('gradeitemid' => $gradeitem->id, 'courseid' => $gradeitem->courseid));
- if ($gbs) {
- return $gbs;
- } else {
- return false;
- }
- } else {
- return false;
+ if ($lineitemid) {
+ return $DB->get_record('ltiservice_gradebookservices',
+ array('gradeitemid' => $lineitemid));
}
}
diff --git a/mod/lti/service/gradebookservices/db/install.xml b/mod/lti/service/gradebookservices/db/install.xml
index 73e8431b941..2bd03b71478 100644
--- a/mod/lti/service/gradebookservices/db/install.xml
+++ b/mod/lti/service/gradebookservices/db/install.xml
@@ -13,6 +13,7 @@
+
diff --git a/mod/lti/service/gradebookservices/db/upgrade.php b/mod/lti/service/gradebookservices/db/upgrade.php
new file mode 100644
index 00000000000..d212447b840
--- /dev/null
+++ b/mod/lti/service/gradebookservices/db/upgrade.php
@@ -0,0 +1,110 @@
+.
+//
+// This file is part of BasicLTI4Moodle
+//
+// BasicLTI4Moodle is an IMS BasicLTI (Basic Learning Tools for Interoperability)
+// consumer for Moodle 1.9 and Moodle 2.0. BasicLTI is a IMS Standard that allows web
+// based learning tools to be easily integrated in LMS as native ones. The IMS BasicLTI
+// specification is part of the IMS standard Common Cartridge 1.1 Sakai and other main LMS
+// are already supporting or going to support BasicLTI. This project Implements the consumer
+// for Moodle. Moodle is a Free Open source Learning Management System by Martin Dougiamas.
+// BasicLTI4Moodle is a project iniciated and leaded by Ludo(Marc Alier) and Jordi Piguillem
+// at the GESSI research group at UPC.
+// SimpleLTI consumer for Moodle is an implementation of the early specification of LTI
+// by Charles Severance (Dr Chuck) htp://dr-chuck.com , developed by Jordi Piguillem in a
+// Google Summer of Code 2008 project co-mentored by Charles Severance and Marc Alier.
+//
+// BasicLTI4Moodle is copyright 2009 by Marc Alier Forment, Jordi Piguillem and Nikolas Galanis
+// of the Universitat Politecnica de Catalunya http://www.upc.edu
+// Contact info: Marc Alier Forment granludo @ gmail.com or marc.alier @ upc.edu.
+
+/**
+ * This file defines tasks performed by the plugin.
+ *
+ * @package ltiservice_gradebookservices
+ * @copyright 2020 Cengage Learning http://www.cengage.com
+ * @author Claude Vervoort
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+ defined('MOODLE_INTERNAL') || die;
+
+/**
+ * xmldb_ltiservice_gradebookservices_upgrade is the function that upgrades
+ * the gradebook lti service subplugin database when is needed.
+ *
+ * This function is automatically called when version number in
+ * version.php changes.
+ *
+ * @param int $oldversion New old version number.
+ *
+ * @return boolean
+ */
+function xmldb_ltiservice_gradebookservices_upgrade($oldversion) {
+ global $CFG, $DB, $OUTPUT;
+
+ $dbman = $DB->get_manager();
+
+ if ($oldversion < 2020020601) {
+ // Define field typeid to be added to lti_tool_settings.
+ $table = new xmldb_table('ltiservice_gradebookservices');
+ $field = new xmldb_field('resourceid', XMLDB_TYPE_CHAR, "512", null, null, null, null);
+
+ // Conditionally launch add field typeid.
+ if (!$dbman->field_exists($table, $field)) {
+ $dbman->add_field($table, $field);
+ }
+
+ // Lti savepoint reached.
+ upgrade_plugin_savepoint(true, 2020020601, 'ltiservice', 'gradebookservices');
+ }
+
+ if ($oldversion < 2020020602) {
+ // Now that we have added the new column let's migrate it'
+ // Prior implementation was storing the resourceid under the grade item idnumber, so moving it to lti_gradebookservices.
+ // We only care for mod/lti grade items as manual columns would already have a matching gradebookservices record.
+
+ $DB->execute("INSERT INTO {ltiservice_gradebookservices} (gradeitemid, courseid, typeid, resourceid, baseurl)
+ SELECT gi.id, courseid, lti.typeid, gi.idnumber, t.baseurl
+ FROM {grade_items} gi
+ JOIN {lti} lti ON lti.id=gi.iteminstance AND gi.itemtype='mod' AND gi.itemmodule='lti'
+ JOIN {lti_types} t ON t.id = lti.typeid
+ WHERE gi.id NOT IN ( SELECT gradeitemid
+ FROM {ltiservice_gradebookservices} )
+ AND gi.idnumber IS NOT NULL
+ AND gi.idnumber <> ''");
+
+ // Lti savepoint reached.
+ upgrade_plugin_savepoint(true, 2020020602, 'ltiservice', 'gradebookservices');
+ }
+
+ if ($oldversion < 2020020603) {
+ // Here updating the resourceid of pre-existing lti_gradebookservices.
+ $DB->execute("UPDATE {ltiservice_gradebookservices}
+ SET resourceid = (SELECT idnumber FROM {grade_items} WHERE id=gradeitemid)
+ WHERE gradeitemid in (SELECT id FROM {grade_items}
+ WHERE ((itemtype='mod' AND itemmodule='lti') OR itemtype='manual')
+ AND idnumber IS NOT NULL
+ AND idnumber <> '')
+ AND (resourceid is null OR resourceid = '')");
+
+ // Lti savepoint reached.
+ upgrade_plugin_savepoint(true, 2020020603, 'ltiservice', 'gradebookservices');
+ }
+
+ return true;
+}
diff --git a/mod/lti/service/gradebookservices/tests/gradebookservices_test.php b/mod/lti/service/gradebookservices/tests/gradebookservices_test.php
new file mode 100644
index 00000000000..d97c2335fce
--- /dev/null
+++ b/mod/lti/service/gradebookservices/tests/gradebookservices_test.php
@@ -0,0 +1,282 @@
+.
+
+/**
+ * Unit tests for mod_lti gradebookservices
+ * @package ltiservice_gradebookservices
+ * @category external
+ * @copyright 2020 Claude Vervoort
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+use ltiservice_gradebookservices\local\service\gradebookservices;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Unit tests for lti gradebookservices.
+ */
+class mod_lti_gradebookservices_testcase extends advanced_testcase {
+
+ /**
+ * Test saving a graded LTI with resource and tag info (as a result of
+ * content item selection) creates a gradebookservices record
+ * that can be retrieved using the gradebook service API.
+ */
+ public function test_lti_add_coupled_lineitem() {
+ global $CFG;
+ require_once($CFG->dirroot . '/mod/lti/locallib.php');
+
+ $this->resetAfterTest();
+ $this->setAdminUser();
+
+ // Create a tool type, associated with that proxy.
+
+ $typeid = $this->create_type();
+ $course = $this->getDataGenerator()->create_course();
+ $resourceid = 'test-resource-id';
+ $tag = 'tag';
+
+ $ltiinstance = $this->create_graded_lti($typeid, $course, $resourceid, $tag);
+
+ $this->assertNotNull($ltiinstance);
+
+ $gbs = gradebookservices::find_ltiservice_gradebookservice_for_lti($ltiinstance->id);
+
+ $this->assertNotNull($gbs);
+ $this->assertEquals($resourceid, $gbs->resourceid);
+ $this->assertEquals($tag, $gbs->tag);
+
+ $this->assert_lineitems($course, $typeid, $ltiinstance->name, $ltiinstance, $resourceid, $tag);
+ }
+
+ /**
+ * Test saving a standalone LTI lineitem with resource and tag info
+ * that can be retrieved using the gradebook service API.
+ */
+ public function test_lti_add_standalone_lineitem() {
+ $this->resetAfterTest();
+ $this->setAdminUser();
+
+ $course = $this->getDataGenerator()->create_course();
+ $resourceid = "test-resource-standalone";
+ $tag = "test-tag-standalone";
+ $typeid = $this->create_type();
+
+ $this->create_standalone_lineitem($course->id, $typeid, $resourceid, $tag);
+
+ $this->assert_lineitems($course, $typeid, "manualtest", null, $resourceid, $tag);
+ }
+
+ /**
+ * Test line item URL is populated for coupled line item only
+ * if there is not another line item bound to the lti instance,
+ * since in that case there would be no rule to define which of
+ * the line items should be actually passed.
+ */
+ public function test_get_launch_parameters_coupled() {
+ global $CFG;
+ require_once($CFG->dirroot . '/mod/lti/locallib.php');
+
+ $this->resetAfterTest();
+ $this->setAdminUser();
+
+ // Create a tool type, associated with that proxy.
+
+ $typeid = $this->create_type();
+ $course = $this->getDataGenerator()->create_course();
+
+ $ltiinstance = $this->create_graded_lti($typeid, $course, 'resource-id', 'tag');
+
+ $this->assertNotNull($ltiinstance);
+
+ $gbservice = new gradebookservices();
+ $params = $gbservice->get_launch_parameters('basic-lti-launch-request', $course->id, 111, $typeid, $ltiinstance->id);
+ $this->assertEquals('$LineItem.url', $params['lineitem_url']);
+ $this->assertEquals('$LineItem.url', $params['lineitem_url']);
+
+ $this->create_standalone_lineitem($course->id, $typeid, 'resource-id', 'tag', $ltiinstance->id);
+ $params = $gbservice->get_launch_parameters('basic-lti-launch-request', $course->id, 111, $typeid, $ltiinstance->id);
+ $this->assertEquals('$LineItems.url', $params['lineitems_url']);
+ // 2 line items for a single link, we cannot return a single line item url.
+ $this->assertFalse(array_key_exists('$LineItem.url', $params));
+ }
+
+ /**
+ * Test line item URL is populated for not coupled line item only
+ * if there is a single line item attached to that lti instance.
+ */
+ public function test_get_launch_parameters_decoupled() {
+ global $CFG;
+ require_once($CFG->dirroot . '/mod/lti/locallib.php');
+
+ $this->resetAfterTest();
+ $this->setAdminUser();
+
+ // Create a tool type, associated with that proxy.
+
+ $typeid = $this->create_type();
+
+ $course = $this->getDataGenerator()->create_course();
+
+ $ltiinstance = $this->create_notgraded_lti($typeid, $course);
+
+ $this->assertNotNull($ltiinstance);
+
+ $gbservice = new gradebookservices();
+ $params = $gbservice->get_launch_parameters('basic-lti-launch-request', $course->id, 111, $typeid, $ltiinstance->id);
+ $this->assertEquals('$LineItems.url', $params['lineitems_url']);
+ $this->assertFalse(array_key_exists('$LineItem.url', $params));
+
+ $this->create_standalone_lineitem($course->id, $typeid, 'resource-id', 'tag', $ltiinstance->id);
+ $params = $gbservice->get_launch_parameters('basic-lti-launch-request', $course->id, 111, $typeid, $ltiinstance->id);
+ $this->assertEquals('$LineItems.url', $params['lineitems_url']);
+ $this->assertEquals('$LineItem.url', $params['lineitem_url']);
+
+ // 2 line items for a single link, we cannot return a single line item url.
+ $this->create_standalone_lineitem($course->id, $typeid, 'resource-id', 'tag-2', $ltiinstance->id);
+ $this->assertFalse(array_key_exists('$LineItem.url', $params));
+ }
+
+ /**
+ * Asserts a matching gradebookservices record exist with the matching tag and resourceid.
+ *
+ * @param object $course current course
+ * @param int $typeid Type id of the tool
+ * @param string $label Label of the line item
+ * @param object|null $ltiinstance lti instance related to that line item
+ * @param string|null $resourceid resourceid the line item should have
+ * @param string|null $tag tag the line item should have
+ */
+ private function assert_lineitems(object $course,
+ int $typeid,
+ string $label,
+ ?object $ltiinstance,
+ ?string $resourceid,
+ ?string $tag) : void {
+ $gbservice = new gradebookservices();
+ $gradeitems = $gbservice->get_lineitems($course->id, null, null, null, null, null, $typeid);
+
+ // The 1st item in the array is the items count.
+ $this->assertEquals(1, $gradeitems[0]);
+
+ $lineitem = gradebookservices::item_for_json($gradeitems[1][0], '', $typeid);
+ $this->assertEquals(10, $lineitem->scoreMaximum);
+ $this->assertEquals($resourceid, $lineitem->resourceId);
+ $this->assertEquals($tag, $lineitem->tag);
+ $this->assertEquals($label, $lineitem->label);
+
+ $gradeitems = $gbservice->get_lineitems($course->id, $resourceid, null, null, null, null, $typeid);
+ $this->assertEquals(1, $gradeitems[0]);
+
+ if (isset($ltiinstance)) {
+ $gradeitems = $gbservice->get_lineitems($course->id, null, $ltiinstance->id, null, null, null, $typeid);
+ $this->assertEquals(1, $gradeitems[0]);
+ $gradeitems = $gbservice->get_lineitems($course->id, null, $ltiinstance->id + 1, null, null, null, $typeid);
+ $this->assertEquals(0, $gradeitems[0]);
+ }
+
+ $gradeitems = $gbservice->get_lineitems($course->id, null, null, $tag, null, null, $typeid);
+ $this->assertEquals(1, $gradeitems[0]);
+
+ $gradeitems = $gbservice->get_lineitems($course->id, 'an unknown resource id', null, null, null, null, $typeid);
+ $this->assertEquals(0, $gradeitems[0]);
+
+ $gradeitems = $gbservice->get_lineitems($course->id, null, null, 'an unknown tag', null, null, $typeid);
+ $this->assertEquals(0, $gradeitems[0]);
+ }
+
+ /**
+ * Inserts a graded lti instance, which should create a grade_item and gradebookservices record.
+ *
+ * @param int $typeid Type ID of the LTI Tool.
+ * @param object $course course where to add the lti instance.
+ * @param string|null $resourceid resource id
+ * @param string|null $tag tag
+ *
+ * @return object lti instance created
+ */
+ private function create_graded_lti(int $typeid, object $course, ?string $resourceid, ?string $tag) : object {
+
+ $lti = ['course' => $course->id,
+ 'typeid' => $typeid,
+ 'instructorchoiceacceptgrades' => LTI_SETTING_ALWAYS,
+ 'grade' => 10,
+ 'lineitemresourceid' => $resourceid,
+ 'lineitemtag' => $tag];
+
+ return $this->getDataGenerator()->create_module('lti', $lti, array());
+ }
+
+ /**
+ * Inserts an lti instance that is not graded.
+ *
+ * @param int $typeid Type Id of the LTI Tool.
+ * @param object $course course where to add the lti instance.
+ *
+ * @return object lti instance created
+ */
+ private function create_notgraded_lti(int $typeid, object $course) : object {
+
+ $lti = ['course' => $course->id,
+ 'typeid' => $typeid,
+ 'instructorchoiceacceptgrades' => LTI_SETTING_NEVER];
+
+ return $this->getDataGenerator()->create_module('lti', $lti, array());
+ }
+
+ /**
+ * Inserts a standalone lineitem (gradeitem, gradebookservices entries).
+ *
+ * @param int $courseid Id of the course where the standalone line item will be added.
+ * @param int $typeid of the LTI Tool
+ * @param string|null $resourceid resource id
+ * @param string|null $tag tag
+ * @param int|null $ltiinstanceid Id of the LTI instance the standalone line item will be related to.
+ *
+ */
+ private function create_standalone_lineitem(int $courseid,
+ int $typeid,
+ ?string $resourceid,
+ ?string $tag,
+ int $ltiinstanceid = null) : void {
+ $gbservice = new gradebookservices();
+ $gbservice->add_standalone_lineitem($courseid,
+ "manualtest",
+ 10,
+ "https://test.phpunit",
+ $ltiinstanceid,
+ $resourceid,
+ $tag,
+ $typeid,
+ null /*toolproxyid*/);
+ }
+
+ /**
+ * Creates a new LTI Tool Type.
+ */
+ private function create_type() {
+ $type = new stdClass();
+ $type->state = LTI_TOOL_STATE_CONFIGURED;
+ $type->name = "Test tool";
+ $type->description = "Example description";
+ $type->clientid = "Test client ID";
+ $type->baseurl = $this->getExternalTestFileUrl('/test.html');
+
+ $config = new stdClass();
+ $config->ltiservice_gradesynchronization = 2;
+ return lti_add_type($type, $config);
+ }
+}
\ No newline at end of file
diff --git a/mod/lti/service/gradebookservices/version.php b/mod/lti/service/gradebookservices/version.php
index 8c02f9edc69..b1b5c624392 100644
--- a/mod/lti/service/gradebookservices/version.php
+++ b/mod/lti/service/gradebookservices/version.php
@@ -25,6 +25,6 @@
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2019111800;
+$plugin->version = 2020020603;
$plugin->requires = 2019111200;
$plugin->component = 'ltiservice_gradebookservices';
diff --git a/mod/lti/tests/lib_test.php b/mod/lti/tests/lib_test.php
index a57073498e3..564b5cff869 100644
--- a/mod/lti/tests/lib_test.php
+++ b/mod/lti/tests/lib_test.php
@@ -26,7 +26,6 @@
defined('MOODLE_INTERNAL') || die();
-
/**
* Unit tests for mod_lti lib
*