From c1fae2b928a0956cd195679e61bb8b3d0e350553 Mon Sep 17 00:00:00 2001 From: Jun Pataleta Date: Tue, 26 Jul 2016 14:40:50 +0800 Subject: [PATCH] MDL-49609 mod_lti: Refactor content-item and make it work using JS * Rebase and resolve conflicts from initial patch. * Reorganise contentitem and contentitem_return pages. * Add capability checks for contentitem and contentitem_return pages. * Move the building of Content-Item selection request to a local lib function. * Move contentitem_return processing logic to a local lib function. * Fix type settings update. Content-item checkbox does not get checked/unchecked on load. * Fix tool settings update. Disabled content-item checkbox gets unchecked when tool settings form is submitted. * Add "Select content" button on load which launches the content item selection dialogue. * Move hardcoded HTML and JS to mustache templates and AMD modules. * Use standard YUI dialog for displaying the Content-Item selection page. * Added processing for the following Content-Item properties: - text - icon * On ContentItem selection, fill out form with the configuration data retrieved, instead of automatically saving the tool and redirecting to the course page. * Removed section- and sectionreturn-related code since we're not automatically adding the tool on ContentItem selection. * On mod_lti_mod_form, enable configuration fields if they support ContentItem selection. * New form-field module for setting fields using JS * Change types_config table's 'value' column from char(255) to text --- mod/lti/amd/build/contentitem.min.js | 1 + mod/lti/amd/build/contentitem_return.min.js | 1 + mod/lti/amd/build/form-field.min.js | 1 + mod/lti/amd/src/contentitem.js | 122 ++++++ mod/lti/amd/src/contentitem_return.js | 40 ++ mod/lti/amd/src/form-field.js | 107 +++++ mod/lti/contentitem.php | 77 +--- mod/lti/contentitem2.php | 196 --------- mod/lti/contentitem_return.php | 206 ++-------- mod/lti/db/install.xml | 2 +- mod/lti/db/upgrade.php | 15 + mod/lti/edit_form.php | 18 +- mod/lti/lang/en/lti.php | 15 +- mod/lti/locallib.php | 377 +++++++++++++++++- mod/lti/mod_form.js | 132 ++---- mod/lti/mod_form.php | 68 +++- .../classes/local/resource/toolproxy.php | 26 +- mod/lti/styles.css | 18 - mod/lti/templates/contentitem.mustache | 88 ++++ mod/lti/version.php | 2 +- 20 files changed, 910 insertions(+), 602 deletions(-) create mode 100644 mod/lti/amd/build/contentitem.min.js create mode 100644 mod/lti/amd/build/contentitem_return.min.js create mode 100644 mod/lti/amd/build/form-field.min.js create mode 100644 mod/lti/amd/src/contentitem.js create mode 100644 mod/lti/amd/src/contentitem_return.js create mode 100644 mod/lti/amd/src/form-field.js delete mode 100644 mod/lti/contentitem2.php create mode 100644 mod/lti/templates/contentitem.mustache diff --git a/mod/lti/amd/build/contentitem.min.js b/mod/lti/amd/build/contentitem.min.js new file mode 100644 index 00000000000..574f74cfa97 --- /dev/null +++ b/mod/lti/amd/build/contentitem.min.js @@ -0,0 +1 @@ +define(["jquery","core/notification","core/str","core/templates","mod_lti/form-field","core/yui"],function(a,b,c,d,e){var f,g={init:function(a,e){var g="";c.get_string("selectcontent","lti").then(function(b){g=b;var c={url:a,postData:e};return d.render("mod_lti/contentitem",c)}).then(function(a,c){f=new M.core.dialogue({modal:!0,headerContent:g,bodyContent:a,draggable:!0,width:"800px",height:"600px"}),f.show(),f.after("visibleChange",function(a){a.prevVal&&!a.newVal&&(this.destroy(),b.fetchNotifications())},f),d.runTemplateJS(c)}).fail(b.exception)}},h=[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)];return window.processContentItemReturnData=function(b){f&&f.hide();var c;for(c in h){var d=h[c],e=null;"undefined"!==a.type(b[d.name])&&(e=b[d.name]),d.setFieldValue(e)}},g}); \ No newline at end of file diff --git a/mod/lti/amd/build/contentitem_return.min.js b/mod/lti/amd/build/contentitem_return.min.js new file mode 100644 index 00000000000..6c08d1a888a --- /dev/null +++ b/mod/lti/amd/build/contentitem_return.min.js @@ -0,0 +1 @@ +define([],function(){return{init:function(a){window!=top&&parent.processContentItemReturnData(a)}}}); \ No newline at end of file diff --git a/mod/lti/amd/build/form-field.min.js b/mod/lti/amd/build/form-field.min.js new file mode 100644 index 00000000000..d71dc09b708 --- /dev/null +++ b/mod/lti/amd/build/form-field.min.js @@ -0,0 +1 @@ +define(["jquery"],function(a){var b=function(a,b,c,d){this.name=a,this.id="id_"+this.name,this.selector="#"+this.id,this.type=b,this.resetIfUndefined=c,this.defaultValue=d};return b.TYPES={TEXT:1,SELECT:2,CHECKBOX:3,EDITOR:4},b.prototype.setFieldValue=function(c){if(null===c){if(!this.resetIfUndefined)return;c=this.defaultValue}switch(this.type){case b.TYPES.CHECKBOX:c?a(this.selector).prop("checked",!0):a(this.selector).prop("checked",!1);break;case b.TYPES.EDITOR:if("undefined"!==a.type(c.text)){var d=a(this.selector+"editable");d.length?d.html(c.text):"undefined"!=typeof tinyMCE&&tinyMCE.execInstanceCommand(this.id,"mceInsertContent",!1,c.text),a(this.selector).val(c.text)}break;default:a(this.selector).val(c)}},b}); \ No newline at end of file diff --git a/mod/lti/amd/src/contentitem.js b/mod/lti/amd/src/contentitem.js new file mode 100644 index 00000000000..cf1fe526332 --- /dev/null +++ b/mod/lti/amd/src/contentitem.js @@ -0,0 +1,122 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Launches the modal dialogue that contains the iframe that sends the Content-Item selection request to an + * LTI tool provider that supports Content-Item type message. + * + * See template: mod_lti/contentitem + * + * @module mod_lti/contentitem + * @class contentitem + * @package mod_lti + * @copyright 2016 Jun Pataleta + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since 3.2 + */ +define(['jquery', 'core/notification', 'core/str', 'core/templates', 'mod_lti/form-field', 'core/yui'], + function($, notification, str, templates, FormField) { + var dialogue; + var contentItem = { + /** + * Init function. + * + * @param {string} url The URL for the content item selection. + * @param {object} postData The data to be sent for the content item selection request. + */ + init: function(url, postData) { + var dialogueTitle = ''; + str.get_string('selectcontent', 'lti').then(function(title) { + dialogueTitle = title; + var context = { + url: url, + postData: postData + }; + return templates.render('mod_lti/contentitem', context); + + }).then(function(html, js) { + // Set dialog's body content. + dialogue = new M.core.dialogue({ + modal: true, + headerContent: dialogueTitle, + bodyContent: html, + draggable: true, + width: '800px', + height: '600px' + }); + + // Show dialog. + dialogue.show(); + + // Destroy after hiding. + dialogue.after('visibleChange', function(e) { + // Going from visible to hidden. + if (e.prevVal && !e.newVal) { + this.destroy(); + // Fetch notifications. + notification.fetchNotifications(); + } + }, dialogue); + + templates.runTemplateJS(js); + + }).fail(notification.exception); + } + }; + + /** + * Array of form fields for LTI tool configuration. + * + * @type {*[]} + */ + var ltiFormFields = [ + new FormField('name', FormField.TYPES.TEXT, false, ''), + new FormField('introeditor', FormField.TYPES.EDITOR, false, ''), + new FormField('toolurl', FormField.TYPES.TEXT, true, ''), + new FormField('securetoolurl', FormField.TYPES.TEXT, true, ''), + new FormField('instructorchoiceacceptgrades', FormField.TYPES.CHECKBOX, true, true), + new FormField('instructorchoicesendname', FormField.TYPES.CHECKBOX, true, true), + new FormField('instructorchoicesendemailaddr', FormField.TYPES.CHECKBOX, true, true), + new FormField('instructorcustomparameters', FormField.TYPES.TEXT, true, ''), + new FormField('icon', FormField.TYPES.TEXT, true, ''), + new FormField('secureicon', FormField.TYPES.TEXT, true, ''), + new FormField('launchcontainer', FormField.TYPES.SELECT, true, 0) + ]; + + /** + * Window function that can be called from mod_lti/contentitem_return to close the dialogue and process the return data. + * + * @param {object} returnData The fetched configuration data from the Content-Item selection dialogue. + */ + window.processContentItemReturnData = function(returnData) { + if (dialogue) { + dialogue.hide(); + } + + // Populate LTI configuration fields from return data. + var index; + for (index in ltiFormFields) { + var field = ltiFormFields[index]; + var value = null; + if ($.type(returnData[field.name]) !== 'undefined') { + value = returnData[field.name]; + } + field.setFieldValue(value); + } + }; + + return contentItem; + } +); diff --git a/mod/lti/amd/src/contentitem_return.js b/mod/lti/amd/src/contentitem_return.js new file mode 100644 index 00000000000..2ae3ceca6f7 --- /dev/null +++ b/mod/lti/amd/src/contentitem_return.js @@ -0,0 +1,40 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Processes the result of LTI tool creation from a Content-Item message type. + * + * @module mod_lti/contentitem_return + * @class contentitem_return + * @package mod_lti + * @copyright 2016 Jun Pataleta + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since 3.2 + */ +define([], function() { + return { + /** + * Init function. + * + * @param {string} returnData The returned data. + */ + init: function(returnData) { + if (window != top) { + // Send return data to be processed by the parent window. + parent.processContentItemReturnData(returnData); + } + } + }; +}); diff --git a/mod/lti/amd/src/form-field.js b/mod/lti/amd/src/form-field.js new file mode 100644 index 00000000000..3d7f445fbef --- /dev/null +++ b/mod/lti/amd/src/form-field.js @@ -0,0 +1,107 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * A module that enables the setting of form field values on the client side. + * + * @module mod_lti/form-field + * @class form-field + * @package mod_lti + * @copyright 2016 Jun Pataleta + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since 3.2 + */ +define(['jquery'], + function($) { + /** + * Form field class. + * + * @param {string} name Field name. + * @param {number} type The field type. + * @param {boolean} resetIfUndefined Flag to reset the field to the default value if undefined in the return data. + * @param {string|number|boolean} defaultValue The default value to use for the field. + * @constructor + */ + var FormField = function(name, type, resetIfUndefined, defaultValue) { + this.name = name; + this.id = 'id_' + this.name; + this.selector = '#' + this.id; + this.type = type; + this.resetIfUndefined = resetIfUndefined; + this.defaultValue = defaultValue; + }; + + /** + * Form field types. + * + * @type {{TEXT: number, SELECT: number, CHECKBOX: number, EDITOR: number}} + */ + FormField.TYPES = { + TEXT: 1, + SELECT: 2, + CHECKBOX: 3, + EDITOR: 4 + }; + + /** + * Sets the values for a form field. + * + * @param {string|boolean|number} value The value to be set into the field. + */ + FormField.prototype.setFieldValue = function(value) { + if (value === null) { + if (this.resetIfUndefined) { + value = this.defaultValue; + } else { + // No need set the field value if value is null and there's no need to reset the field. + return; + } + } + + switch (this.type) { + case FormField.TYPES.CHECKBOX: + if (value) { + $(this.selector).prop('checked', true); + } else { + $(this.selector).prop('checked', false); + } + break; + case FormField.TYPES.EDITOR: + if ($.type(value.text) !== 'undefined') { + /* global tinyMCE:false */ + + // Set text in editor's editable content, if applicable. + // Check if it is an Atto editor. + var attoEditor = $(this.selector + 'editable'); + if (attoEditor.length) { + attoEditor.html(value.text); + } else if (typeof tinyMCE !== 'undefined') { + // If the editor is not Atto, try to fallback to TinyMCE. + tinyMCE.execInstanceCommand(this.id, 'mceInsertContent', false, value.text); + } + + // Set text to actual editor text area. + $(this.selector).val(value.text); + } + break; + default: + $(this.selector).val(value); + break; + } + }; + + return FormField; + } +); diff --git a/mod/lti/contentitem.php b/mod/lti/contentitem.php index d3f549b2b16..0b6b87281d5 100644 --- a/mod/lti/contentitem.php +++ b/mod/lti/contentitem.php @@ -15,7 +15,7 @@ // along with Moodle. If not, see . /** - * Display a page containing an iframe for the content-item selection process. + * Handle sending a user to a tool provider to initiate a content-item selection. * * @package mod_lti * @copyright 2015 Vital Source Technologies http://vitalsource.com @@ -24,66 +24,33 @@ */ require_once('../../config.php'); -require_once($CFG->dirroot.'/mod/lti/lib.php'); -require_once($CFG->dirroot.'/mod/lti/locallib.php'); +require_once($CFG->dirroot . '/mod/lti/lib.php'); +require_once($CFG->dirroot . '/mod/lti/locallib.php'); -$courseid = required_param('course', PARAM_INT); -$sectionid = required_param('section', PARAM_INT); $id = required_param('id', PARAM_INT); -$sectionreturn = required_param('sr', PARAM_INT); - -$title = optional_param('title', null, PARAM_TEXT); +$courseid = required_param('course', PARAM_INT); +$title = optional_param('title', '', PARAM_TEXT); +$text = optional_param('text', '', PARAM_RAW); +// Check access and capabilities. $course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST); - require_login($course); +$context = context_course::instance($courseid); +require_capability('moodle/course:manageactivities', $context); +require_capability('mod/lti:addcoursetool', $context); -$url = new moodle_url('/mod/lti/contentitem.php', array('course' => $courseid)); +// Set the return URL. We send the launch container along to help us avoid frames-within-frames when the user returns. +$returnurlparams = [ + 'course' => $course->id, + 'id' => $id, + 'sesskey' => sesskey() +]; +$returnurl = new \moodle_url('/mod/lti/contentitem_return.php', $returnurlparams); -$contentitem = new moodle_url('/mod/lti/contentitem2.php', - array('course' => $courseid, 'section' => $sectionid, 'id' => $id, 'sr' => $sectionreturn, 'title' => $title)); +// Prepare the request. +$request = lti_build_content_item_selection_request($id, $course, $returnurl, $title, $text, [], []); -echo "

\n"; -echo get_string('register_warning', 'lti'); -echo "\n

\n"; +// Get the launch HTML. +$content = lti_post_launch_html($request->params, $request->url, false); -echo ''; - -// Output script to make the object tag be as large as possible. -$resize = ' - -'; - -echo $resize; +echo $content; diff --git a/mod/lti/contentitem2.php b/mod/lti/contentitem2.php deleted file mode 100644 index 3e974370bfc..00000000000 --- a/mod/lti/contentitem2.php +++ /dev/null @@ -1,196 +0,0 @@ -. - -/** - * Handle sending a user to a tool provider to initiate a content-item selection. - * - * @package mod_lti - * @copyright 2015 Vital Source Technologies http://vitalsource.com - * @author Stephen Vickers - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -require_once("../../config.php"); -require_once($CFG->dirroot.'/mod/lti/lib.php'); -require_once($CFG->dirroot.'/mod/lti/locallib.php'); - -$courseid = required_param('course', PARAM_INT); -$sectionid = required_param('section', PARAM_INT); -$id = required_param('id', PARAM_INT); -$sectionreturn = required_param('sr', PARAM_INT); - -require_login($PAGE->course); - -$tool = lti_get_type($id); -$typeconfig = lti_get_type_config($id); -if (isset($tool->toolproxyid)) { - $toolproxy = lti_get_tool_proxy($tool->toolproxyid); - $key = $toolproxy->guid; - $secret = $toolproxy->secret; -} else { - $toolproxy = null; - if (!empty($instance->resourcekey)) { - $key = $instance->resourcekey; - } else if (!empty($typeconfig['resourcekey'])) { - $key = $typeconfig['resourcekey']; - } else { - $key = ''; - } - if (!empty($instance->password)) { - $secret = $instance->password; - } else if (!empty($typeconfig['password'])) { - $secret = $typeconfig['password']; - } else { - $secret = ''; - } -} -$tool->enabledcapability = $typeconfig['enabledcapability_ContentItemSelectionRequest']; -$tool->parameter = $typeconfig['parameter_ContentItemSelectionRequest']; - -$title = optional_param('title', $tool->name, PARAM_TEXT); - -if (isset($typeconfig['toolurl_ContentItemSelectionRequest'])) { - $endpoint = $typeconfig['toolurl_ContentItemSelectionRequest']; -} else { - $endpoint = !empty($instance->toolurl) ? $instance->toolurl : $typeconfig['toolurl']; -} -$endpoint = trim($endpoint); - -// If the current request is using SSL and a secure tool URL is specified, use it. -if (lti_request_is_using_ssl() && !empty($instance->securetoolurl)) { - $endpoint = trim($instance->securetoolurl); -} - -// If SSL is forced, use the secure tool url if specified. Otherwise, make sure https is on the normal launch URL. -if (isset($typeconfig['forcessl']) && ($typeconfig['forcessl'] == '1')) { - if (!empty($instance->securetoolurl)) { - $endpoint = trim($instance->securetoolurl); - } - - $endpoint = lti_ensure_url_is_https($endpoint); -} else { - if (!strstr($endpoint, '://')) { - $endpoint = 'http://' . $endpoint; - } -} - -$orgid = (isset($typeconfig['organizationid'])) ? $typeconfig['organizationid'] : ''; - -$course = $PAGE->course; -$islti2 = isset($tool->toolproxyid); -$instance = new stdClass(); -$instance->course = $courseid; -$allparams = lti_build_request($instance, $typeconfig, $course, $id, $islti2); -if ($islti2) { - $requestparams = lti_build_request_lti2($tool, $allparams); -} else { - $requestparams = $allparams; -} -$requestparams = array_merge($requestparams, lti_build_standard_request(null, $orgid, $islti2)); -$customstr = ''; -if (isset($typeconfig['customparameters'])) { - $customstr = $typeconfig['customparameters']; -} -$requestparams = array_merge($requestparams, lti_build_custom_parameters($toolproxy, $tool, $instance, $allparams, $customstr, - '', $islti2)); - -// Allow request params to be updated by sub-plugins. -$plugins = core_component::get_plugin_list('ltisource'); -foreach (array_keys($plugins) as $plugin) { - $pluginparams = component_callback('ltisource_'.$plugin, 'before_launch', - array($instance, $endpoint, $requestparams), array()); - - if (!empty($pluginparams) && is_array($pluginparams)) { - $requestparams = array_merge($requestparams, $pluginparams); - } -} - -if ($islti2) { - $requestparams['lti_version'] = 'LTI-2p0'; -} else { - $requestparams['lti_version'] = 'LTI-1p0'; -} -$requestparams['lti_message_type'] = 'ContentItemSelectionRequest'; - -$requestparams['accept_media_types'] = 'application/vnd.ims.lti.v1.ltilink'; -$requestparams['accept_presentation_document_targets'] = 'frame,iframe,window'; -$requestparams['accept_unsigned'] = 'false'; -$requestparams['accept_multiple'] = 'false'; -$requestparams['auto_create'] = 'true'; -$requestparams['can_confirm'] = 'false'; -$requestparams['accept_copy_advice'] = 'false'; -$requestparams['title'] = $title; - -$returnurlparams = array('course' => $courseid, - 'section' => $sectionid, - 'id' => $id, - 'sr' => $sectionreturn, - 'sesskey' => sesskey()); - -// Add the return URL. We send the launch container along to help us avoid frames-within-frames when the user returns. -$url = new \moodle_url('/mod/lti/contentitem_return.php', $returnurlparams); -$returnurl = $url->out(false); - -if (isset($typeconfig['forcessl']) && ($typeconfig['forcessl'] == '1')) { - $returnurl = lti_ensure_url_is_https($returnurl); -} - -$requestparams['content_item_return_url'] = $returnurl; - - -$parms = lti_sign_parameters($requestparams, $endpoint, "POST", $key, $secret); - -$endpointurl = new \moodle_url($endpoint); -$endpointparams = $endpointurl->params(); - -// Strip querystring params in endpoint url from $parms to avoid duplication. -if (!empty($endpointparams) && !empty($parms)) { - foreach (array_keys($endpointparams) as $paramname) { - if (isset($parms[$paramname])) { - unset($parms[$paramname]); - } - } -} - -echo "

\n"; -echo get_string('register_warning', 'lti'); -echo "\n

\n"; - -$loading = $OUTPUT->render(new \pix_icon('i/loading', '', 'moodle', - array('style' => 'margin:auto;vertical-align:middle;margin-top:125px;', - 'opacity' => '0.5'))); - -echo "

\n"; -echo $loading; -echo "\n

\n"; - -$script = ' - -'; - -echo $script; - -$content = lti_post_launch_html($parms, $endpoint, false); - -echo $content; diff --git a/mod/lti/contentitem_return.php b/mod/lti/contentitem_return.php index c30485b21a7..53cc5de0195 100644 --- a/mod/lti/contentitem_return.php +++ b/mod/lti/contentitem_return.php @@ -24,203 +24,53 @@ */ require_once('../../config.php'); -require_once($CFG->dirroot . '/course/modlib.php'); -require_once($CFG->dirroot . '/mod/lti/lib.php'); require_once($CFG->dirroot . '/mod/lti/locallib.php'); -require_once($CFG->dirroot . '/mod/lti/OAuth.php'); -require_once($CFG->dirroot . '/mod/lti/TrivialStore.php'); -use moodle\mod\lti as lti; - -$courseid = required_param('course', PARAM_INT); -$sectionid = required_param('section', PARAM_INT); $id = required_param('id', PARAM_INT); -$sectionreturn = required_param('sr', PARAM_INT); +$courseid = required_param('course', PARAM_INT); $messagetype = required_param('lti_message_type', PARAM_TEXT); $version = required_param('lti_version', PARAM_TEXT); -$consumer_key = required_param('oauth_consumer_key', PARAM_RAW); - +$consumerkey = required_param('oauth_consumer_key', PARAM_RAW); $items = optional_param('content_items', '', PARAM_RAW); $errormsg = optional_param('lti_errormsg', '', PARAM_TEXT); $msg = optional_param('lti_msg', '', PARAM_TEXT); $course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST); -$module = $DB->get_record('modules', array('name' => 'lti'), '*', MUST_EXIST); -$tool = lti_get_type($id); -$typeconfig = lti_get_type_config($id); - require_login($course); require_sesskey(); +$context = context_course::instance($courseid); +require_capability('moodle/course:manageactivities', $context); +require_capability('mod/lti:addcoursetool', $context); -if (isset($tool->toolproxyid)) { - $toolproxy = lti_get_tool_proxy($tool->toolproxyid); - $key = $toolproxy->guid; - $secret = $toolproxy->secret; -} else { - $toolproxy = null; - if (!empty($instance->resourcekey)) { - $key = $instance->resourcekey; - } else if (!empty($typeconfig['resourcekey'])) { - $key = $typeconfig['resourcekey']; - } else { - $key = ''; - } - if (!empty($instance->password)) { - $secret = $instance->password; - } else if (!empty($typeconfig['password'])) { - $secret = $typeconfig['password']; - } else { - $secret = ''; +$redirecturl = null; +$returndata = null; +if (empty($errormsg) && !empty($items)) { + try { + $returndata = lti_tool_configuration_from_content_item($id, $messagetype, $version, $consumerkey, $items); + } catch (moodle_exception $e) { + $errormsg = $e->getMessage(); } } -if ($consumer_key !== $key) { - throw new Exception('Consumer key is incorrect.'); -} +$pageurl = new moodle_url('/mod/lti/contentitem_return.php'); +$PAGE->set_url($pageurl); +$PAGE->set_pagelayout('popup'); +echo $OUTPUT->header(); -$store = new lti\TrivialOAuthDataStore(); -$store->add_consumer($key, $secret); +// Call JS module to redirect the user to the course page or close the dialogue on error/cancel. +$PAGE->requires->js_call_amd('mod_lti/contentitem_return', 'init', [$returndata]); -$server = new lti\OAuthServer($store); +echo $OUTPUT->footer(); -$method = new lti\OAuthSignatureMethod_HMAC_SHA1(); -$server->add_signature_method($method); -$request = lti\OAuthRequest::from_request(); +// Add messages to notification stack for rendering later. +if ($errormsg) { + // Content item selection has encountered an error. + \core\notification::error($errormsg); -try { - $server->verify_request($request); -} catch (\Exception $e) { - $message = $e->getMessage(); - debugging($e->getMessage() . "\n"); - throw new lti\OAuthException("OAuth signature failed: " . $message); -} - -if ($items) { - $items = json_decode($items); - if ($items->{'@context'} !== 'http://purl.imsglobal.org/ctx/lti/v1/ContentItem') { - throw new Exception('Invalid media type.'); - } - if (!isset($items->{'@graph'}) || !is_array($items->{'@graph'}) || (count($items->{'@graph'}) > 1)) { - throw new Exception('Invalid format.'); - } -} - -$continueurl = course_get_url($course, $sectionid, array('sr' => $sectionreturn)); -if (count($items->{'@graph'}) > 0) { - foreach ($items->{'@graph'} as $item) { - $moduleinfo = new stdClass(); - $moduleinfo->modulename = 'lti'; - $moduleinfo->name = ''; - if (isset($item->title)) { - $moduleinfo->name = $item->title; - } - if (empty($moduleinfo->name)) { - $moduleinfo->name = $tool->name; - } - $moduleinfo->module = $module->id; - $moduleinfo->section = $sectionid; - $moduleinfo->visible = 1; - if (isset($item->url)) { - $moduleinfo->toolurl = $item->url; - $moduleinfo->typeid = 0; - } else { - $moduleinfo->typeid = $id; - } - $moduleinfo->instructorchoicesendname = LTI_SETTING_NEVER; - $moduleinfo->instructorchoicesendemailaddr = LTI_SETTING_NEVER; - $moduleinfo->instructorchoiceacceptgrades = LTI_SETTING_NEVER; - $moduleinfo->launchcontainer = LTI_LAUNCH_CONTAINER_DEFAULT; - if (isset($item->placementAdvice->presentationDocumentTarget)) { - if ($item->placementAdvice->presentationDocumentTarget === 'window') { - $moduleinfo->launchcontainer = LTI_LAUNCH_CONTAINER_WINDOW; - } else if ($item->placementAdvice->presentationDocumentTarget === 'frame') { - $moduleinfo->launchcontainer = LTI_LAUNCH_CONTAINER_EMBED_NO_BLOCKS; - } else if ($item->placementAdvice->presentationDocumentTarget === 'iframe') { - $moduleinfo->launchcontainer = LTI_LAUNCH_CONTAINER_EMBED; - } - } - if (isset($item->custom)) { - $moduleinfo->instructorcustomparameters = ''; - $first = true; - foreach ($item->custom as $key => $value) { - if (!$first) { - $moduleinfo->instructorcustomparameters .= "\n"; - } - $moduleinfo->instructorcustomparameters .= "{$key}={$value}"; - $first = false; - } - } - $moduleinfo = add_moduleinfo($moduleinfo, $course, null); - } - $clickhere = get_string('click_to_continue', 'lti', (object)array('link' => $continueurl->out())); -} else { - $clickhere = get_string('return_to_course', 'lti', (object)array('link' => $continueurl->out())); -} - -if (!empty($errormsg) || !empty($msg)) { - - $url = new moodle_url('/mod/lti/contentitem_return.php', - array('course' => $courseid)); - $PAGE->set_url($url); - - $pagetitle = strip_tags($course->shortname); - $PAGE->set_title($pagetitle); - $PAGE->set_heading($course->fullname); - - $PAGE->set_pagelayout('embedded'); - - echo $OUTPUT->header(); - - if (!empty($lti) and !empty($context)) { - echo $OUTPUT->heading(format_string($lti->name, true, array('context' => $context))); - } - - if (!empty($errormsg)) { - - echo '

'; - echo get_string('lti_launch_error', 'lti') . ' '; - p($errormsg); - echo "

\n"; - +} else if (!empty($returndata)) { + // Means success. + if (!$msg) { + $msg = get_string('successfullyfetchedtoolconfigurationfromcontent', 'lti'); } - - if (!empty($msg)) { - - echo '

'; - p($msg); - echo "

\n"; - - } - - echo "

{$clickhere}

"; - - echo $OUTPUT->footer(); - -} else { - - $url = $continueurl->out(); - - echo ''; - - $script = " - - "; - - $noscript = " - - "; - - echo $script; - echo $noscript; - - echo ''; - + \core\notification::success($msg); } diff --git a/mod/lti/db/install.xml b/mod/lti/db/install.xml index 2fbbec06f32..040f32edae3 100644 --- a/mod/lti/db/install.xml +++ b/mod/lti/db/install.xml @@ -95,7 +95,7 @@ - + diff --git a/mod/lti/db/upgrade.php b/mod/lti/db/upgrade.php index 01d3131097f..ce19319d830 100644 --- a/mod/lti/db/upgrade.php +++ b/mod/lti/db/upgrade.php @@ -197,5 +197,20 @@ function xmldb_lti_upgrade($oldversion) { // Moodle v3.1.0 release upgrade line. // Put any upgrade step following this. + // Moodle v3.2.0 release upgrade line. + // Put any upgrade step following this. + if ($oldversion < 2016052301) { + + // Changing type of field value on table lti_types_config to text. + $table = new xmldb_table('lti_types_config'); + $field = new xmldb_field('value', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null, 'name'); + + // Launch change of type for field value. + $dbman->change_field_type($table, $field); + + // Lti savepoint reached. + upgrade_mod_savepoint(true, 2016052301, 'lti'); + } + return true; } diff --git a/mod/lti/edit_form.php b/mod/lti/edit_form.php index 23a22c98a42..bb1849cf93b 100644 --- a/mod/lti/edit_form.php +++ b/mod/lti/edit_form.php @@ -101,7 +101,6 @@ class mod_lti_edit_types_form extends moodleform{ $mform->addElement('textarea', 'lti_customparameters', get_string('custom', 'lti'), array('rows' => 4, 'cols' => 60)); $mform->setType('lti_customparameters', PARAM_TEXT); $mform->addHelpButton('lti_customparameters', 'custom', 'lti'); - $mform->setAdvanced('lti_customparameters'); if (!empty($this->_customdata->isadmin)) { $options = array( @@ -137,9 +136,8 @@ class mod_lti_edit_types_form extends moodleform{ $mform->setDefault('lti_launchcontainer', LTI_LAUNCH_CONTAINER_EMBED_NO_BLOCKS); $mform->addHelpButton('lti_launchcontainer', 'default_launch_container', 'lti'); $mform->setType('lti_launchcontainer', PARAM_INT); - $mform->setAdvanced('lti_launchcontainer'); - $mform->addElement('checkbox', 'lti_contentitem', ' ', ' ' . get_string('contentitem', 'lti')); + $mform->addElement('advcheckbox', 'lti_contentitem', get_string('contentitem', 'lti')); $mform->addHelpButton('lti_contentitem', 'contentitem', 'lti'); $mform->setAdvanced('lti_contentitem'); if ($istool) { @@ -238,4 +236,18 @@ class mod_lti_edit_types_form extends moodleform{ $this->add_action_buttons(); } + + /** + * Retrieves the data of the submitted form. + * + * @return stdClass + */ + public function get_data() { + $data = parent::get_data(); + if ($data && !empty($this->_customdata->istool)) { + // Content item checkbox is disabled in tool settings, so this cannot be edited. Just unset it. + unset($data->lti_contentitem); + } + return $data; + } } diff --git a/mod/lti/lang/en/lti.php b/mod/lti/lang/en/lti.php index ec533ff5393..cc7df136075 100644 --- a/mod/lti/lang/en/lti.php +++ b/mod/lti/lang/en/lti.php @@ -105,12 +105,10 @@ $string['configtoolurl'] = 'Default remote tool URL'; $string['configtypes'] = 'Enable LTI applications'; $string['configured'] = 'Configured'; $string['confirmtoolactivation'] = 'Are you sure you would like to activate this tool?'; -$string['courseactivitiesorresources'] = 'Course activities or resources'; +$string['contentitem'] = 'Content-Item Message'; +$string['contentitem_help'] = 'If ticked, the option \'Configure tool from link\' will be available when adding an external tool.'; $string['course_tool_types'] = 'Course tools'; -$string['configure_item'] = 'Configure item'; -$string['contentitem'] = 'Tool supports Content-Item message'; -$string['contentitem_help'] = 'If selected, the tool provider will participate in the creation of new links added to courses.'; -$string['course_tool_types'] = 'Course tool types'; +$string['courseactivitiesorresources'] = 'Course activities or resources'; $string['courseid'] = 'Course ID number'; $string['courseinformation'] = 'Course information'; $string['courselink'] = 'Go to course'; @@ -169,7 +167,12 @@ $string['enableemailnotification'] = 'Send notification emails'; $string['enableemailnotification_help'] = 'If enabled, students will receive email notification when their tool submissions are graded.'; $string['enterkeyandsecret'] = 'Enter your consumer key and shared secret'; $string['errorbadurl'] = 'URL is not a valid tool URL or cartridge.'; +$string['errorincorrectconsumerkey'] = 'Consumer key is incorrect.'; +$string['errorinvaliddata'] = 'Invalid data: {$a}'; +$string['errorinvalidmediatype'] = 'Invalid media type: {$a}'; +$string['errorinvalidresponseformat'] = 'Invalid Content-Item response format.'; $string['errormisconfig'] = 'Misconfigured tool. Please ask your Moodle administrator to fix the configuration of the tool.'; +$string['errortooltypenotfound'] = 'LTI tool type not found.'; $string['existing_window'] = 'Existing window'; $string['extensions'] = 'LTI extension services'; $string['external_tool_type'] = 'Preconfigured tool'; @@ -407,6 +410,7 @@ $string['secure_launch_url'] = 'Secure launch URL'; $string['secure_launch_url_help'] = 'Similar to the launch URL, but used instead of the launch URL if high security is required. Moodle will use the secure launch URL instead of the launch URL if the Moodle site is accessed through SSL, or if the tool configuration is set to always launch through SSL. The launch URL may also be set to an https address to force launching through SSL, and this field may be left blank.'; +$string['selectcontent'] = 'Select content'; $string['send'] = 'Send'; $string['services'] = 'Services'; $string['services_help'] = 'Select those services which you wish to offer to the tool provider. More than one service can be selected.'; @@ -448,6 +452,7 @@ $string['submission'] = 'Submission'; $string['submissions'] = 'Submissions'; $string['submissionsfor'] = 'Submissions for {$a}'; $string['successfullycreatedtooltype'] = 'Successfully created new tool!'; +$string['successfullyfetchedtoolconfigurationfromcontent'] = 'Successfully fetched tool configuration from the selected content.'; $string['subplugintype_ltiresource'] = 'LTI service resource'; $string['subplugintype_ltiresource_plural'] = 'LTI service resources'; $string['subplugintype_ltiservice'] = 'LTI service'; diff --git a/mod/lti/locallib.php b/mod/lti/locallib.php index 832470dc3a0..ece6c9f41ab 100644 --- a/mod/lti/locallib.php +++ b/mod/lti/locallib.php @@ -55,6 +55,8 @@ use moodle\mod\lti as lti; require_once($CFG->dirroot.'/mod/lti/OAuth.php'); require_once($CFG->libdir.'/weblib.php'); +require_once($CFG->dirroot . '/course/modlib.php'); +require_once($CFG->dirroot . '/mod/lti/TrivialStore.php'); define('LTI_URL_DOMAIN_REGEX', '/(?:https?:\/\/)?(?:www\.)?([^\/]+)(?:\/|$)/i'); @@ -83,6 +85,9 @@ define('LTI_COURSEVISIBLE_NO', 0); define('LTI_COURSEVISIBLE_PRECONFIGURED', 1); define('LTI_COURSEVISIBLE_ACTIVITYCHOOSER', 2); +define('LTI_VERSION_1', 'LTI-1p0'); +define('LTI_VERSION_2', 'LTI-2p0'); + /** * Return the launch data required for opening the external tool. * @@ -396,17 +401,6 @@ function lti_build_request($instance, $typeconfig, $course, $typeid = null, $isl $intro = str_replace("\n", "\r\n", $intro); $requestparams['resource_link_description'] = $intro; } - if (!empty($instance->servicesalt)) { - $placementsecret = $instance->servicesalt; - if ( isset($placementsecret) && ($islti2 || - $typeconfig['acceptgrades'] == LTI_SETTING_ALWAYS || - ($typeconfig['acceptgrades'] == LTI_SETTING_DELEGATE && - $instance->instructorchoiceacceptgrades == LTI_SETTING_ALWAYS))) { - - $sourcedid = json_encode(lti_build_sourcedid($instance->id, $USER->id, $placementsecret, $typeid)); - $requestparams['lis_result_sourcedid'] = $sourcedid; - } - } if (!empty($instance->id)) { $requestparams['resource_link_id'] = $instance->id; } @@ -420,10 +414,11 @@ function lti_build_request($instance, $typeconfig, $course, $typeid = null, $isl $requestparams['lis_course_section_sourcedid'] = $course->idnumber; } - if ( !empty($instance->id) && isset($placementsecret) && ($islti2 || - $typeconfig['acceptgrades'] == LTI_SETTING_ALWAYS || - ($typeconfig['acceptgrades'] == LTI_SETTING_DELEGATE && $instance->instructorchoiceacceptgrades == LTI_SETTING_ALWAYS))) { - + if (!empty($instance->id) && !empty($instance->servicesalt) && ($islti2 || + $typeconfig['acceptgrades'] == LTI_SETTING_ALWAYS || + ($typeconfig['acceptgrades'] == LTI_SETTING_DELEGATE && $instance->instructorchoiceacceptgrades == LTI_SETTING_ALWAYS)) + ) { + $placementsecret = $instance->servicesalt; $sourcedid = json_encode(lti_build_sourcedid($instance->id, $USER->id, $placementsecret, $typeid)); $requestparams['lis_result_sourcedid'] = $sourcedid; @@ -445,7 +440,9 @@ function lti_build_request($instance, $typeconfig, $course, $typeid = null, $isl // Send user's name and email data if appropriate. if ($islti2 || $typeconfig['sendname'] == LTI_SETTING_ALWAYS || - ( $typeconfig['sendname'] == LTI_SETTING_DELEGATE && $instance->instructorchoicesendname == LTI_SETTING_ALWAYS ) ) { + ($typeconfig['sendname'] == LTI_SETTING_DELEGATE && isset($instance->instructorchoicesendname) + && $instance->instructorchoicesendname == LTI_SETTING_ALWAYS) + ) { $requestparams['lis_person_name_given'] = $USER->firstname; $requestparams['lis_person_name_family'] = $USER->lastname; $requestparams['lis_person_name_full'] = $USER->firstname . ' ' . $USER->lastname; @@ -453,7 +450,9 @@ function lti_build_request($instance, $typeconfig, $course, $typeid = null, $isl } if ($islti2 || $typeconfig['sendemailaddr'] == LTI_SETTING_ALWAYS || - ($typeconfig['sendemailaddr'] == LTI_SETTING_DELEGATE && $instance->instructorchoicesendemailaddr == LTI_SETTING_ALWAYS)) { + ($typeconfig['sendemailaddr'] == LTI_SETTING_DELEGATE && isset($instance->instructorchoicesendemailaddr) + && $instance->instructorchoicesendemailaddr == LTI_SETTING_ALWAYS) + ) { $requestparams['lis_person_contact_email_primary'] = $USER->email; } @@ -492,13 +491,14 @@ function lti_build_request_lti2($tool, $params) { /** * This function builds the standard parameters for an LTI 1 or 2 request that must be sent to the tool producer * - * @param object $instance Basic LTI instance object + * @param stdClass $instance Basic LTI instance object * @param string $orgid Organisation ID * @param boolean $islti2 True if an LTI 2 tool is being launched + * @param string $messagetype The request message type. Defaults to basic-lti-launch-request if empty. * * @return array Request details */ -function lti_build_standard_request($instance, $orgid, $islti2) { +function lti_build_standard_request($instance, $orgid, $islti2, $messagetype = 'basic-lti-launch-request') { global $CFG; $requestparams = array(); @@ -525,7 +525,7 @@ function lti_build_standard_request($instance, $orgid, $islti2) { } else { $requestparams['lti_version'] = 'LTI-2p0'; } - $requestparams['lti_message_type'] = 'basic-lti-launch-request'; + $requestparams['lti_message_type'] = $messagetype; if ($orgid) { $requestparams["tool_consumer_instance_guid"] = $orgid; @@ -586,6 +586,343 @@ function lti_build_custom_parameters($toolproxy, $tool, $instance, $params, $cus return $custom; } +/** + * Builds a standard LTI Content-Item selection request. + * + * @param int $id The tool type ID. + * @param stdClass $course The course object. + * @param moodle_url $returnurl The return URL in the tool consumer (TC) that the tool provider (TP) + * will use to return the Content-Item message. + * @param string $title The tool's title, if available. + * @param string $text The text to display to represent the content item. This value may be a long description of the content item. + * @param array $mediatypes Array of MIME types types supported by the TC. If empty, the TC will support ltilink by default. + * @param array $presentationtargets Array of ways in which the selected content item(s) can be requested to be opened + * (via the presentationDocumentTarget element for a returned content item). + * If empty, "frame", "iframe", and "window" will be supported by default. + * @param bool $autocreate Indicates whether any content items returned by the TP would be automatically persisted without + * @param bool $multiple Indicates whether the user should be permitted to select more than one item. False by default. + * any option for the user to cancel the operation. False by default. + * @param bool $unsigned Indicates whether the TC is willing to accept an unsigned return message, or not. + * A signed message should always be required when the content item is being created automatically in the + * TC without further interaction from the user. False by default. + * @param bool $canconfirm Flag for can_confirm parameter. False by default. + * @param bool $copyadvice Indicates whether the TC is able and willing to make a local copy of a content item. False by default. + * @return stdClass The object containing the signed request parameters and the URL to the TP's Content-Item selection interface. + * @throws moodle_exception When the LTI tool type does not exist.` + * @throws coding_exception For invalid media type and presentation target parameters. + */ +function lti_build_content_item_selection_request($id, $course, moodle_url $returnurl, $title = '', $text = '', $mediatypes = [], + $presentationtargets = [], $autocreate = false, $multiple = false, + $unsigned = false, $canconfirm = false, $copyadvice = false) { + $tool = lti_get_type($id); + // Validate parameters. + if (!$tool) { + throw new moodle_exception('errortooltypenotfound', 'mod_lti'); + } + if (!is_array($mediatypes)) { + throw new coding_exception('The list of accepted media types should be in an array'); + } + if (!is_array($presentationtargets)) { + throw new coding_exception('The list of accepted presentation targets should be in an array'); + } + + // Check title. If empty, use the tool's name. + if (empty($title)) { + $title = $tool->name; + } + + $typeconfig = lti_get_type_config($id); + $key = ''; + $secret = ''; + $islti2 = false; + if (isset($tool->toolproxyid)) { + $islti2 = true; + $toolproxy = lti_get_tool_proxy($tool->toolproxyid); + $key = $toolproxy->guid; + $secret = $toolproxy->secret; + } else { + $toolproxy = null; + if (!empty($typeconfig['resourcekey'])) { + $key = $typeconfig['resourcekey']; + } + if (!empty($typeconfig['password'])) { + $secret = $typeconfig['password']; + } + } + $tool->enabledcapability = ''; + if (!empty($typeconfig['enabledcapability_ContentItemSelectionRequest'])) { + $tool->enabledcapability = $typeconfig['enabledcapability_ContentItemSelectionRequest']; + } + + $tool->parameter = ''; + if (!empty($typeconfig['parameter_ContentItemSelectionRequest'])) { + $tool->parameter = $typeconfig['parameter_ContentItemSelectionRequest']; + } + + // Set the tool URL. + if (!empty($typeconfig['toolurl_ContentItemSelectionRequest'])) { + $toolurl = new moodle_url($typeconfig['toolurl_ContentItemSelectionRequest']); + } else { + $toolurl = new moodle_url($typeconfig['toolurl']); + } + + // Check if SSL is forced. + if (!empty($typeconfig['forcessl'])) { + // Make sure the tool URL is set to https. + if (strtolower($toolurl->get_scheme()) === 'http') { + $toolurl->set_scheme('https'); + } + // Make sure the return URL is set to https. + if (strtolower($returnurl->get_scheme()) === 'http') { + $returnurl->set_scheme('https'); + } + } + $toolurlout = $toolurl->out(false); + + // Get base request parameters. + $instance = new stdClass(); + $instance->course = $course->id; + $requestparams = lti_build_request($instance, $typeconfig, $course, $id, $islti2); + + // Get LTI2-specific request parameters and merge to the request parameters if applicable. + if ($islti2) { + $lti2params = lti_build_request_lti2($tool, $requestparams); + $requestparams = array_merge($requestparams, $lti2params); + } + + // Get standard request parameters and merge to the request parameters. + $orgid = !empty($typeconfig['organizationid']) ? $typeconfig['organizationid'] : ''; + $standardparams = lti_build_standard_request(null, $orgid, $islti2, 'ContentItemSelectionRequest'); + $requestparams = array_merge($requestparams, $standardparams); + + // Get custom request parameters and merge to the request parameters. + $customstr = ''; + if (!empty($typeconfig['customparameters'])) { + $customstr = $typeconfig['customparameters']; + } + $customparams = lti_build_custom_parameters($toolproxy, $tool, $instance, $requestparams, $customstr, '', $islti2); + $requestparams = array_merge($requestparams, $customparams); + + // Allow request params to be updated by sub-plugins. + $plugins = core_component::get_plugin_list('ltisource'); + foreach (array_keys($plugins) as $plugin) { + $pluginparams = component_callback('ltisource_' . $plugin, 'before_launch', [$instance, $toolurlout, $requestparams], []); + + if (!empty($pluginparams) && is_array($pluginparams)) { + $requestparams = array_merge($requestparams, $pluginparams); + } + } + + // Media types. Set to ltilink by default if empty. + if (empty($mediatypes)) { + $mediatypes = [ + 'application/vnd.ims.lti.v1.ltilink', + ]; + } + $requestparams['accept_media_types'] = implode(',', $mediatypes); + + // Presentation targets. Supports frame, iframe, window by default if empty. + if (empty($presentationtargets)) { + $presentationtargets = [ + 'frame', + 'iframe', + 'window', + ]; + } + $requestparams['accept_presentation_document_targets'] = implode(',', $presentationtargets); + + // Other request parameters. + $requestparams['accept_copy_advice'] = $copyadvice === true ? 'true' : 'false'; + $requestparams['accept_multiple'] = $multiple === true ? 'true' : 'false'; + $requestparams['accept_unsigned'] = $unsigned === true ? 'true' : 'false'; + $requestparams['auto_create'] = $autocreate === true ? 'true' : 'false'; + $requestparams['can_confirm'] = $canconfirm === true ? 'true' : 'false'; + $requestparams['content_item_return_url'] = $returnurl->out(false); + $requestparams['title'] = $title; + $requestparams['text'] = $text; + $signedparams = lti_sign_parameters($requestparams, $toolurlout, 'POST', $key, $secret); + $toolurlparams = $toolurl->params(); + + // Strip querystring params in endpoint url from $signedparams to avoid duplication. + if (!empty($toolurlparams) && !empty($signedparams)) { + foreach (array_keys($toolurlparams) as $paramname) { + if (isset($signedparams[$paramname])) { + unset($signedparams[$paramname]); + } + } + } + + // Check for params that should not be passed. Unset if they are set. + $unwantedparams = [ + 'resource_link_id', + 'resource_link_title', + 'resource_link_description', + 'launch_presentation_return_url', + 'lis_result_sourcedid', + ]; + foreach ($unwantedparams as $param) { + if (isset($signedparams[$param])) { + unset($signedparams[$param]); + } + } + + // Prepare result object. + $result = new stdClass(); + $result->params = $signedparams; + $result->url = $toolurlout; + + return $result; +} + +/** + * Processes the tool provider's response to the ContentItemSelectionRequest and builds the configuration data from the + * selected content item. This configuration data can be then used when adding a tool into the course. + * + * @param int $typeid The tool type ID. + * @param string $messagetype The value for the lti_message_type parameter. + * @param string $ltiversion The value for the lti_version parameter. + * @param string $consumerkey The consumer key. + * @param string $contentitemsjson The JSON string for the content_items parameter. + * @return stdClass The array of module information objects. + * @throws moodle_exception + * @throws lti\OAuthException + */ +function lti_tool_configuration_from_content_item($typeid, $messagetype, $ltiversion, $consumerkey, $contentitemsjson) { + $tool = lti_get_type($typeid); + // Validate parameters. + if (!$tool) { + throw new moodle_exception('errortooltypenotfound', 'mod_lti'); + } + // Check lti_message_type. Show debugging if it's not set to ContentItemSelection. + // No need to throw exceptions for now since lti_message_type does not seem to be used in this processing at the moment. + if ($messagetype !== 'ContentItemSelection') { + debugging("lti_message_type is invalid: {$messagetype}. It should be set to 'ContentItemSelection'.", + DEBUG_DEVELOPER); + } + + $typeconfig = lti_get_type_config($typeid); + + if (isset($tool->toolproxyid)) { + $islti2 = true; + $toolproxy = lti_get_tool_proxy($tool->toolproxyid); + $key = $toolproxy->guid; + $secret = $toolproxy->secret; + } else { + $islti2 = false; + $toolproxy = null; + if (!empty($typeconfig['resourcekey'])) { + $key = $typeconfig['resourcekey']; + } else { + $key = ''; + } + if (!empty($typeconfig['password'])) { + $secret = $typeconfig['password']; + } else { + $secret = ''; + } + } + + // Check LTI versions from our side and the response's side. Show debugging if they don't match. + // No need to throw exceptions for now since LTI version does not seem to be used in this processing at the moment. + $expectedversion = LTI_VERSION_1; + if ($islti2) { + $expectedversion = LTI_VERSION_2; + } + if ($ltiversion !== $expectedversion) { + debugging("lti_version from response does not match the tool's configuration. Tool: {$expectedversion}," . + " Response: {$ltiversion}", DEBUG_DEVELOPER); + } + + if ($consumerkey !== $key) { + throw new moodle_exception('errorincorrectconsumerkey', 'mod_lti'); + } + + $store = new lti\TrivialOAuthDataStore(); + $store->add_consumer($key, $secret); + $server = new lti\OAuthServer($store); + $method = new lti\OAuthSignatureMethod_HMAC_SHA1(); + $server->add_signature_method($method); + $request = lti\OAuthRequest::from_request(); + try { + $server->verify_request($request); + } catch (lti\OAuthException $e) { + throw new lti\OAuthException("OAuth signature failed: " . $e->getMessage()); + } + + $items = json_decode($contentitemsjson); + if (empty($items)) { + throw new moodle_exception('errorinvaliddata', 'mod_lti', '', $contentitemsjson); + } + if ($items->{'@context'} !== 'http://purl.imsglobal.org/ctx/lti/v1/ContentItem') { + throw new moodle_exception('errorinvalidmediatype', 'mod_lti', '', $items->{'@context'}); + } + if (!isset($items->{'@graph'}) || !is_array($items->{'@graph'}) || (count($items->{'@graph'}) > 1)) { + throw new moodle_exception('errorinvalidresponseformat', 'mod_lti'); + } + + $config = null; + if (!empty($items->{'@graph'})) { + $item = $items->{'@graph'}[0]; + + $config = new stdClass(); + $config->name = ''; + if (isset($item->title)) { + $config->name = $item->title; + } + if (empty($config->name)) { + $config->name = $tool->name; + } + if (isset($item->text)) { + $config->introeditor = [ + 'text' => $item->text, + 'format' => FORMAT_PLAIN + ]; + } + if (isset($item->icon->{'@id'})) { + $iconurl = new moodle_url($item->icon->{'@id'}); + // Assign item's icon URL to secureicon or icon depending on its scheme. + if (strtolower($iconurl->get_scheme()) === 'https') { + $config->secureicon = $iconurl->out(false); + } else { + $config->icon = $iconurl->out(false); + } + } + if (isset($item->url)) { + $url = new moodle_url($item->url); + // Assign item URL to securetoolurl or toolurl depending on its scheme. + if (strtolower($url->get_scheme()) === 'https') { + $config->securetoolurl = $url->out(false); + } else { + $config->toolurl = $url->out(false); + } + $config->typeid = 0; + } else { + $config->typeid = $typeid; + } + $config->instructorchoicesendname = LTI_SETTING_NEVER; + $config->instructorchoicesendemailaddr = LTI_SETTING_NEVER; + $config->instructorchoiceacceptgrades = LTI_SETTING_NEVER; + $config->launchcontainer = LTI_LAUNCH_CONTAINER_DEFAULT; + if (isset($item->placementAdvice->presentationDocumentTarget)) { + if ($item->placementAdvice->presentationDocumentTarget === 'window') { + $config->launchcontainer = LTI_LAUNCH_CONTAINER_WINDOW; + } else if ($item->placementAdvice->presentationDocumentTarget === 'frame') { + $config->launchcontainer = LTI_LAUNCH_CONTAINER_EMBED_NO_BLOCKS; + } else if ($item->placementAdvice->presentationDocumentTarget === 'iframe') { + $config->launchcontainer = LTI_LAUNCH_CONTAINER_EMBED; + } + } + if (isset($item->custom)) { + $customparameters = []; + foreach ($item->custom as $key => $value) { + $customparameters[] = "{$key}={$value}"; + } + $config->instructorcustomparameters = implode("\n", $customparameters); + } + } + return $config; +} + function lti_get_tool_table($tools, $id) { global $CFG, $OUTPUT, $USER; $html = ''; diff --git a/mod/lti/mod_form.js b/mod/lti/mod_form.js index 5095b95e292..1044aed3991 100644 --- a/mod/lti/mod_form.js +++ b/mod/lti/mod_form.js @@ -19,26 +19,11 @@ * @package mod * @subpackage lti * @copyright Copyright (c) 2011 Moodlerooms Inc. (http://www.moodlerooms.com) - * @copyright 2015 Vital Source Technologies http://vitalsource.com - * @author Stephen Vickers * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ (function(){ var Y; - var LTICIP = { - BASE : 'base', - }; - - var CSS = { - PANEL : 'lti-contentitem-panel', - HIDDEN : 'hidden', - WRAP : 'lticip-wrap', - HEADER : 'lticip-header', - CLOSE : 'close', - CONTENT : 'lticip-content', - }; - M.mod_lti = M.mod_lti || {}; M.mod_lti.LTI_SETTING_NEVER = 0; @@ -46,13 +31,6 @@ M.mod_lti.LTI_SETTING_DELEGATE = 2; M.mod_lti.editor = { - _base : null, - _escCloseEvent : null, - _headingNode : null, - _contentNode : null, - _toolList : null, - _onResize : null, - _timer : null, init: function(yui3, settings){ if(yui3){ Y = yui3; @@ -71,22 +49,10 @@ self.updateAutomaticToolMatch(Y.one('#id_securetoolurl')); }; - var create = Y.Node.create; - this._base = create('
') - .append(create('
') - .append(create('
') - .append(create('
')) - .append(create('

'+M.util.get_string('configure_item', 'lti')+'

'))) - .append(create('
' + - '
')) - ); - Y.one('.lti_contentitem').on('change', this.show, this); - this._base.one('.'+CSS.HEADER+' .'+CSS.CLOSE).on('click', this.hide, this); - this._headingNode = this._base.one('.'+CSS.HEADER); - this._contentNode = this._base.one('.'+CSS.CONTENT); - Y.one(document.body).append(this._base); - + var contentItemButton = Y.one('[name="selectcontent"]'); + var contentItemUrl = contentItemButton.getAttribute('data-contentitemurl'); var typeSelector = Y.one('#id_typeid'); + typeSelector.on('change', function(e){ updateToolMatches(); @@ -103,7 +69,28 @@ allowgrades.set('checked', !self.getSelectedToolTypeOption().getAttribute('nogrades')); self.toggleGradeSection(); } + }); + // Handle configure from link button click. + contentItemButton.on('click', function() { + var contentItemId = self.getContentItemId(); + if (contentItemId) { + // Get activity name and description values. + var title = Y.one('#id_name').get('value').trim(); + var text = Y.one('#id_introeditor').get('value').trim(); + + // Set data to be POSTed. + var postData = { + id: contentItemId, + course: self.settings.courseId, + title: title, + text: text + }; + + require(['mod_lti/contentitem'], function(contentitem) { + contentitem.init(contentItemUrl, postData); + }); + } }); this.createTypeEditorButtons(); @@ -531,68 +518,17 @@ }); }, - contentItem: function(el){ - var opt = el.options[el.selectedIndex]; - if(opt.getAttribute('contentitem') == '1') { - window.location.href = opt.getAttribute('contentitemurl') + '&title=' + - encodeURIComponent(document.getElementById('id_name').value); + /** + * Gets the tool type ID of the selected tool that supports Content-Item selection. + * + * @returns {number|boolean} The ID of the tool type if it supports Content-Item selection. False, otherwise. + */ + getContentItemId: function() { + var selected = this.getSelectedToolTypeOption(); + if (selected.getAttribute('data-contentitem')) { + return selected.getAttribute('data-id'); } - }, - - show : function(e) { - var opt = e.target.get('options').item(e.target.get('selectedIndex')); - if (opt.getAttribute('contentitem') == '1') { - this._toolList = e.target; - e.preventDefault(); - e.halt(); - this._timer = window.setTimeout(this.doReveal, 20000); - this._base.removeClass(CSS.HIDDEN); - var w = this._base.get('winWidth') * 0.8; - var h = this._base.get('winHeight') * 0.8; - var x = (this._base.get('winWidth') - w) / 2; - var y = (this._base.get('winHeight') - h) / 2; - this._base.setStyle('width', '' + w + 'px'); - this._base.setStyle('height', '' + h + 'px'); - this._base.setXY([x,y]); - - var padding = 15; //The bottom of the iframe wasn\'t visible on some themes. Probably because of border widths, etc. - - var viewportHeight = h - parseInt(this._headingNode.getStyle('height')) - padding; - - this._escCloseEvent = Y.on('key', this.hide, document.body, 'down:27', this); - - var url = opt.getAttribute('contentitemurl') + '&title=' + - encodeURIComponent(Y.one('#id_name').get('value')); - - var ifr = Y.one('.id_contentitem_if'); - ifr.setAttribute('src', url); - ifr.on('load', this.loaded, this); - ifr.setStyle("height", viewportHeight); - - var frm = Y.one('.mform'); - frm.reset(); - } - }, - - doReveal : function() { - var el = Y.one('#id_warning'); - el.removeClass(CSS.HIDDEN); - }, - - loaded : function() { - window.clearTimeout(this._timer); - }, - - hide : function(e) { - this.loaded(); - if (this._escCloseEvent) { - this._escCloseEvent.detach(); - this._escCloseEvent = null; - } - this._base.addClass(CSS.HIDDEN); - var ifr = Y.one('.id_contentitem_if'); - ifr.setAttribute('src', 'about:blank'); + return false; } - }; })(); diff --git a/mod/lti/mod_form.php b/mod/lti/mod_form.php index a75498cc47c..7219faa924d 100644 --- a/mod/lti/mod_form.php +++ b/mod/lti/mod_form.php @@ -43,8 +43,6 @@ * @author Jordi Piguillem * @author Nikolas Galanis * @author Chris Scribner - * @copyright 2015 Vital Source Technologies http://vitalsource.com - * @author Stephen Vickers * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ @@ -56,12 +54,11 @@ require_once($CFG->dirroot.'/mod/lti/locallib.php'); class mod_lti_mod_form extends moodleform_mod { public function definition() { - global $DB, $PAGE, $OUTPUT, $USER, $COURSE, $sesskey, $section; + global $PAGE, $OUTPUT, $COURSE; if ($type = optional_param('type', false, PARAM_ALPHA)) { component_callback("ltisource_$type", 'add_instance_hook'); } - $sectionreturn = optional_param('sr', 0, PARAM_INT); $this->typeid = 0; @@ -109,6 +106,9 @@ class mod_lti_mod_form extends moodleform_mod { $mform->addHelpButton('typeid', 'external_tool_type', 'lti'); $toolproxy = array(); + // Array of tool type IDs that don't support ContentItemSelectionRequest. + $noncontentitemtypes = ['0']; + foreach (lti_get_types_for_add_instance() as $id => $type) { if (!empty($type->toolproxyid)) { $toolproxy[] = $type->id; @@ -133,27 +133,46 @@ class mod_lti_mod_form extends moodleform_mod { } if (!$update && $id) { $config = lti_get_type_config($id); - if (isset($config['contentitem']) && $config['contentitem']) { - $contentitemurl = new moodle_url('/mod/lti/contentitem2.php', - array('course' => $COURSE->id, 'section' => $section, 'id' => $id, 'sr' => $sectionreturn)); - $attributes['contentitem'] = 1; - $attributes['contentitemurl'] = $contentitemurl->out(false); + if (!empty($config['contentitem'])) { + $attributes['data-contentitem'] = 1; + $attributes['data-id'] = $id; + } else { + $noncontentitemtypes[] = $id; } } $tooltypes->addOption($type->name, $id, $attributes); } + // Add button that launches the content-item selection dialogue. + + // Set contentitem URL. + $contentitemurl = new moodle_url('/mod/lti/contentitem.php'); + $contentbuttonattributes['data-contentitemurl'] = $contentitemurl->out(false); + $mform->addElement('button', 'selectcontent', get_string('selectcontent', 'lti'), $contentbuttonattributes); + if ($update) { + $mform->disabledIf('selectcontent', 'typeid', 'neq', 0); + } else { + $mform->disabledIf('selectcontent', 'typeid', 'in', $noncontentitemtypes); + } $mform->addElement('text', 'toolurl', get_string('launch_url', 'lti'), array('size' => '64')); $mform->setType('toolurl', PARAM_URL); $mform->addHelpButton('toolurl', 'launch_url', 'lti'); - $mform->disabledIf('toolurl', 'typeid', 'neq', '0'); + if ($update) { + $mform->disabledIf('toolurl', 'typeid', 'neq', 0); + } else { + $mform->disabledIf('toolurl', 'typeid', 'in', $noncontentitemtypes); + } $mform->addElement('text', 'securetoolurl', get_string('secure_launch_url', 'lti'), array('size' => '64')); $mform->setType('securetoolurl', PARAM_URL); $mform->setAdvanced('securetoolurl'); $mform->addHelpButton('securetoolurl', 'secure_launch_url', 'lti'); - $mform->disabledIf('securetoolurl', 'typeid', 'neq', '0'); + if ($update) { + $mform->disabledIf('securetoolurl', 'typeid', 'neq', 0); + } else { + $mform->disabledIf('securetoolurl', 'typeid', 'in', $noncontentitemtypes); + } $mform->addElement('hidden', 'urlmatchedtypeid', '', array( 'id' => 'id_urlmatchedtypeid' )); $mform->setType('urlmatchedtypeid', PARAM_INT); @@ -174,13 +193,21 @@ class mod_lti_mod_form extends moodleform_mod { $mform->setType('resourcekey', PARAM_TEXT); $mform->setAdvanced('resourcekey'); $mform->addHelpButton('resourcekey', 'resourcekey', 'lti'); - $mform->disabledIf('resourcekey', 'typeid', 'neq', '0'); + if ($update) { + $mform->disabledIf('resourcekey', 'typeid', 'neq', 0); + } else { + $mform->disabledIf('resourcekey', 'typeid', 'in', $noncontentitemtypes); + } $mform->addElement('passwordunmask', 'password', get_string('password', 'lti')); $mform->setType('password', PARAM_TEXT); $mform->setAdvanced('password'); $mform->addHelpButton('password', 'password', 'lti'); - $mform->disabledIf('password', 'typeid', 'neq', '0'); + if ($update) { + $mform->disabledIf('password', 'typeid', 'neq', 0); + } else { + $mform->disabledIf('password', 'typeid', 'in', $noncontentitemtypes); + } $mform->addElement('textarea', 'instructorcustomparameters', get_string('custom', 'lti'), array('rows' => 4, 'cols' => 60)); $mform->setType('instructorcustomparameters', PARAM_TEXT); @@ -191,13 +218,21 @@ class mod_lti_mod_form extends moodleform_mod { $mform->setType('icon', PARAM_URL); $mform->setAdvanced('icon'); $mform->addHelpButton('icon', 'icon_url', 'lti'); - $mform->disabledIf('icon', 'typeid', 'neq', '0'); + if ($update) { + $mform->disabledIf('icon', 'typeid', 'neq', 0); + } else { + $mform->disabledIf('icon', 'typeid', 'in', $noncontentitemtypes); + } $mform->addElement('text', 'secureicon', get_string('secure_icon_url', 'lti'), array('size' => '64')); $mform->setType('secureicon', PARAM_URL); $mform->setAdvanced('secureicon'); $mform->addHelpButton('secureicon', 'secure_icon_url', 'lti'); - $mform->disabledIf('secureicon', 'typeid', 'neq', '0'); + if ($update) { + $mform->disabledIf('secureicon', 'typeid', 'neq', 0); + } else { + $mform->disabledIf('secureicon', 'typeid', 'in', $noncontentitemtypes); + } // Add privacy preferences fieldset where users choose whether to send their data. $mform->addElement('header', 'privacy', get_string('privacy', 'lti')); @@ -264,8 +299,7 @@ class mod_lti_mod_form extends moodleform_mod { array('tooltypedeleted', 'lti'), array('tooltypenotdeleted', 'lti'), array('tooltypeupdated', 'lti'), - array('forced_help', 'lti'), - array('configure_item', 'lti') + array('forced_help', 'lti') ), ); diff --git a/mod/lti/service/toolproxy/classes/local/resource/toolproxy.php b/mod/lti/service/toolproxy/classes/local/resource/toolproxy.php index 9f146e291dc..79f59e08be9 100644 --- a/mod/lti/service/toolproxy/classes/local/resource/toolproxy.php +++ b/mod/lti/service/toolproxy/classes/local/resource/toolproxy.php @@ -149,6 +149,10 @@ class toolproxy extends \mod_lti\local\ltiservice\resource_base { // Extract all launchable tools from the resource handlers. if ($ok) { $resources = $toolproxyjson->tool_profile->resource_handler; + $messagetypes = [ + 'basic-lti-launch-request', + 'ContentItemSelectionRequest', + ]; foreach ($resources as $resource) { $launchable = false; $messages = array(); @@ -165,8 +169,7 @@ class toolproxy extends \mod_lti\local\ltiservice\resource_base { } foreach ($resource->message as $message) { - if (($message->message_type === 'basic-lti-launch-request') || - ($message->message_type === 'ContentItemSelectionRequest')) { + if (in_array($message->message_type, $messagetypes)) { $launchable = $launchable || ($message->message_type === 'basic-lti-launch-request'); $messages[$message->message_type] = $message; } @@ -196,26 +199,29 @@ class toolproxy extends \mod_lti\local\ltiservice\resource_base { } foreach ($tools as $tool) { $messages = $tool->messages; + $launchrequest = $messages['basic-lti-launch-request']; $config = new \stdClass(); - $config->lti_toolurl = "{$baseurl}{$messages['basic-lti-launch-request']->path}"; + $config->lti_toolurl = "{$baseurl}{$launchrequest->path}"; $config->lti_typename = $tool->name; $config->lti_coursevisible = 1; $config->lti_forcessl = 0; if (isset($messages['ContentItemSelectionRequest'])) { + $contentitemrequest = $messages['ContentItemSelectionRequest']; $config->lti_contentitem = 1; - if ($messages['basic-lti-launch-request']->path !== $messages['ContentItemSelectionRequest']->path) { - $config->lti_toolurl_ContentItemSelectionRequest = - "{$baseurl}{$messages['ContentItemSelectionRequest']->path}"; + if ($launchrequest->path !== $contentitemrequest->path) { + $config->lti_toolurl_ContentItemSelectionRequest = $baseurl . $contentitemrequest->path; } - $config->lti_enabledcapability_ContentItemSelectionRequest = implode("\n", $messages['ContentItemSelectionRequest']->enabled_capability); - $config->lti_parameter_ContentItemSelectionRequest = self::lti_extract_parameters($messages['ContentItemSelectionRequest']->parameter); + $contentitemcapabilities = implode("\n", $contentitemrequest->enabled_capability); + $config->lti_enabledcapability_ContentItemSelectionRequest = $contentitemcapabilities; + $contentitemparams = self::lti_extract_parameters($contentitemrequest->parameter); + $config->lti_parameter_ContentItemSelectionRequest = $contentitemparams; } $type = new \stdClass(); $type->state = LTI_TOOL_STATE_PENDING; $type->toolproxyid = $toolproxy->id; - $type->enabledcapability = implode("\n", $messages['basic-lti-launch-request']->enabled_capability); - $type->parameter = self::lti_extract_parameters($messages['basic-lti-launch-request']->parameter); + $type->enabledcapability = implode("\n", $launchrequest->enabled_capability); + $type->parameter = self::lti_extract_parameters($launchrequest->parameter); if (!empty($tool->iconpath)) { $type->icon = "{$baseurl}{$tool->iconpath}"; diff --git a/mod/lti/styles.css b/mod/lti/styles.css index c2e9cbe20c3..66a23e0f6d5 100644 --- a/mod/lti/styles.css +++ b/mod/lti/styles.css @@ -380,21 +380,3 @@ border: 1px solid #ddd; border-radius: 4px; } - -/* Styles for mod_form.php to support Content-Item */ -.lti-contentitem-panel {width:400px;background-color:#666;position:absolute;top:10%;left:10%;border:1px solid #666;border-width:0 5px 5px 0;} -.lti-contentitem-panel.hidden {display:none;} -.lti-contentitem-panel .lticip-wrap {margin-top:-5px;margin-left:-5px;background-color:#FFF;border:1px solid #999;height:inherit;} - -.lti-contentitem-panel .lticip-header {background-color:#eee;padding:1px;} -.lti-contentitem-panel .lticip-header h2 {margin:3px 1em 0.5em 1em;font-size:1em;} -.lti-contentitem-panel .lticip-header .close {width:25px;height:15px;position:absolute;top:2px;right:1em;cursor:pointer;background:url("../../../../mod/lti/pix/close.png") no-repeat scroll 0 0 transparent;} - -.lti-contentitem-panel .lticip-content {text-align:center;position:relative;width:100%;border-top:1px solid #999;border-bottom:1px solid #999;} -.lti-contentitem-panel .lticip-ajax-content {overflow:auto;} - -.lti-contentitem-panel .lticip-loading-lightbox {position:absolute;width:100%;height:100%;top:0;left:0;background-color:#FFF;min-width:50px;min-height:50px;} -.lti-contentitem-panel .lticip-loading-lightbox.hidden {display:none;} -.lti-contentitem-panel .lticip-loading-lightbox .loading-icon {margin:auto;vertical-align:middle;margin-top:125px;} - -.dir-rtl .lti-contentitem-panel .lticip-header .close {right: auto;left:1em;} diff --git a/mod/lti/templates/contentitem.mustache b/mod/lti/templates/contentitem.mustache new file mode 100644 index 00000000000..aa317342705 --- /dev/null +++ b/mod/lti/templates/contentitem.mustache @@ -0,0 +1,88 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template mod_lti/contentitem + + Provides a template for the creation of a new external tool instance via the content-item message. + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Context variables required for this template: + * url The URL the iframe has to load. + * postData The JSON object that contains the information to be POSTed for the ContentItemSelectionRequest. + + Example context (json): + { + "url": "/", + "postData": { + "id": "1", + "course": "1", + "title": "Sample title", + "text": "This is a description" + } + } + +}} +
+
+ {{> mod_lti/loader }} +

{{#str}} loadinghelp, moodle {{/str}}

+ +
+
+ +
+ + + + +
+
+
+{{#js}} + require(['jquery'], function($) { + var loadingContainer = $('.contentitem-loading-container'); + var iframe = $('#contentitem-page-iframe'); + var timeout = setTimeout(function () { + var failedContainer = $('#tool-loading-failed'); + failedContainer.removeClass('hidden'); + }, 20000); + + // Submit form. + $('#contentitem-request-form').submit(); + + iframe.on('load', function() { + loadingContainer.addClass('hidden'); + iframe.removeClass('hidden'); + + // Adjust iframe's width to the fit the container's width. + var containerWidth = $('div.contentitem-container').width(); + $('#contentitem-page-iframe').attr('width', containerWidth); + + var dialogueContentDiv = $('div.contentitem-container').parent(); + var dialogueContainer = dialogueContentDiv.parent(); + // Adjust iframe's height to container's height - 55px (dialogue title bar + top/bottom margins). + var containerHeight = dialogueContainer.height() - 55; + $('#contentitem-page-iframe').attr('height', containerHeight); + }); + }); +{{/js}} diff --git a/mod/lti/version.php b/mod/lti/version.php index ce66b5156dd..5549b1f5975 100644 --- a/mod/lti/version.php +++ b/mod/lti/version.php @@ -48,7 +48,7 @@ defined('MOODLE_INTERNAL') || die; -$plugin->version = 2016052300; // The current module version (Date: YYYYMMDDXX). +$plugin->version = 2016052301; // The current module version (Date: YYYYMMDDXX). $plugin->requires = 2016051900; // Requires this Moodle version. $plugin->component = 'mod_lti'; // Full name of the plugin (used for diagnostics). $plugin->cron = 0;