From d8f9109a12467fcd549290f4fc3b968f9c7e72cb Mon Sep 17 00:00:00 2001 From: spvickers Date: Sat, 21 Mar 2015 01:10:38 +1100 Subject: [PATCH] MDL-49609 mod_lti: Add Content-Item message Added option for tools to support Content-Item message and redirect to/from tool provider when creating an instance. --- mod/lti/contentitem.php | 89 +++++++ mod/lti/contentitem2.php | 196 +++++++++++++++ mod/lti/contentitem_return.php | 226 ++++++++++++++++++ mod/lti/edit_form.php | 9 + mod/lti/lang/en/lti.php | 4 + mod/lti/locallib.php | 64 +++-- mod/lti/mod_form.js | 100 ++++++++ mod/lti/mod_form.php | 24 +- .../classes/local/resource/toolproxy.php | 33 ++- mod/lti/styles.css | 18 ++ 10 files changed, 729 insertions(+), 34 deletions(-) create mode 100644 mod/lti/contentitem.php create mode 100644 mod/lti/contentitem2.php create mode 100644 mod/lti/contentitem_return.php diff --git a/mod/lti/contentitem.php b/mod/lti/contentitem.php new file mode 100644 index 00000000000..d3f549b2b16 --- /dev/null +++ b/mod/lti/contentitem.php @@ -0,0 +1,89 @@ +. + +/** + * Display a page containing an iframe for the content-item selection process. + * + * @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); + +$title = optional_param('title', null, PARAM_TEXT); + +$course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST); + +require_login($course); + +$url = new moodle_url('/mod/lti/contentitem.php', array('course' => $courseid)); + +$contentitem = new moodle_url('/mod/lti/contentitem2.php', + array('course' => $courseid, 'section' => $sectionid, 'id' => $id, 'sr' => $sectionreturn, 'title' => $title)); + +echo "

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

\n"; + +echo ''; + +// Output script to make the object tag be as large as possible. +$resize = ' + +'; + +echo $resize; diff --git a/mod/lti/contentitem2.php b/mod/lti/contentitem2.php new file mode 100644 index 00000000000..3e974370bfc --- /dev/null +++ b/mod/lti/contentitem2.php @@ -0,0 +1,196 @@ +. + +/** + * 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 new file mode 100644 index 00000000000..c30485b21a7 --- /dev/null +++ b/mod/lti/contentitem_return.php @@ -0,0 +1,226 @@ +. + +/** + * Handle the return from the Tool Provider after selecting a content item. + * + * @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 . '/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); +$messagetype = required_param('lti_message_type', PARAM_TEXT); +$version = required_param('lti_version', PARAM_TEXT); +$consumer_key = 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(); + +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 = ''; + } +} + +if ($consumer_key !== $key) { + throw new Exception('Consumer key is incorrect.'); +} + +$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 (\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"; + + } + + 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 ''; + +} diff --git a/mod/lti/edit_form.php b/mod/lti/edit_form.php index 754608daa9c..23a22c98a42 100644 --- a/mod/lti/edit_form.php +++ b/mod/lti/edit_form.php @@ -101,6 +101,7 @@ 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( @@ -136,6 +137,14 @@ 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->addHelpButton('lti_contentitem', 'contentitem', 'lti'); + $mform->setAdvanced('lti_contentitem'); + if ($istool) { + $mform->disabledIf('lti_contentitem', null); + } $mform->addElement('hidden', 'oldicon'); $mform->setType('oldicon', PARAM_URL); diff --git a/mod/lti/lang/en/lti.php b/mod/lti/lang/en/lti.php index 86d64c6ec11..ec533ff5393 100644 --- a/mod/lti/lang/en/lti.php +++ b/mod/lti/lang/en/lti.php @@ -107,6 +107,10 @@ $string['configured'] = 'Configured'; $string['confirmtoolactivation'] = 'Are you sure you would like to activate this tool?'; $string['courseactivitiesorresources'] = 'Course activities or resources'; $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['courseid'] = 'Course ID number'; $string['courseinformation'] = 'Course information'; $string['courselink'] = 'Go to course'; diff --git a/mod/lti/locallib.php b/mod/lti/locallib.php index ddec86456c4..832470dc3a0 100644 --- a/mod/lti/locallib.php +++ b/mod/lti/locallib.php @@ -376,18 +376,7 @@ function lti_build_request($instance, $typeconfig, $course, $typeid = null, $isl $role = lti_get_ims_role($USER, $instance->cmid, $instance->course, $islti2); - $intro = ''; - if (!empty($instance->cmid)) { - $intro = format_module_intro('lti', $instance, $instance->cmid); - $intro = html_to_text($intro, 0, false); - - // This may look weird, but this is required for new lines - // so we generate the same OAuth signature as the tool provider. - $intro = str_replace("\n", "\r\n", $intro); - } $requestparams = array( - 'resource_link_title' => $instance->name, - 'resource_link_description' => $intro, 'user_id' => $USER->id, 'lis_person_sourcedid' => $USER->idnumber, 'roles' => $role, @@ -395,6 +384,29 @@ function lti_build_request($instance, $typeconfig, $course, $typeid = null, $isl 'context_label' => $course->shortname, 'context_title' => $course->fullname, ); + if (!empty($instance->name)) { + $requestparams['resource_link_title'] = $instance->name; + } + if (!empty($instance->cmid)) { + $intro = format_module_intro('lti', $instance, $instance->cmid); + $intro = html_to_text($intro, 0, false); + + // This may look weird, but this is required for new lines + // so we generate the same OAuth signature as the tool provider. + $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; } @@ -407,7 +419,6 @@ function lti_build_request($instance, $typeconfig, $course, $typeid = null, $isl $requestparams['context_type'] = 'CourseSection'; $requestparams['lis_course_section_sourcedid'] = $course->idnumber; } - $placementsecret = $instance->servicesalt; if ( !empty($instance->id) && isset($placementsecret) && ($islti2 || $typeconfig['acceptgrades'] == LTI_SETTING_ALWAYS || @@ -492,9 +503,11 @@ function lti_build_standard_request($instance, $orgid, $islti2) { $requestparams = array(); - $requestparams['resource_link_id'] = $instance->id; - if (property_exists($instance, 'resource_link_id') and !empty($instance->resource_link_id)) { - $requestparams['resource_link_id'] = $instance->resource_link_id; + if ($instance) { + $requestparams['resource_link_id'] = $instance->id; + if (property_exists($instance, 'resource_link_id') and !empty($instance->resource_link_id)) { + $requestparams['resource_link_id'] = $instance->resource_link_id; + } } $requestparams['launch_presentation_locale'] = current_language(); @@ -560,10 +573,14 @@ function lti_build_custom_parameters($toolproxy, $tool, $instance, $params, $cus $tool->parameter, true), $custom); $settings = lti_get_tool_settings($tool->toolproxyid); $custom = array_merge($custom, lti_get_custom_parameters($toolproxy, $tool, $params, $settings)); - $settings = lti_get_tool_settings($tool->toolproxyid, $instance->course); - $custom = array_merge($custom, lti_get_custom_parameters($toolproxy, $tool, $params, $settings)); - $settings = lti_get_tool_settings($tool->toolproxyid, $instance->course, $instance->id); - $custom = array_merge($custom, lti_get_custom_parameters($toolproxy, $tool, $params, $settings)); + if (!empty($instance->course)) { + $settings = lti_get_tool_settings($tool->toolproxyid, $instance->course); + $custom = array_merge($custom, lti_get_custom_parameters($toolproxy, $tool, $params, $settings)); + if (!empty($instance->id)) { + $settings = lti_get_tool_settings($tool->toolproxyid, $instance->course, $instance->id); + $custom = array_merge($custom, lti_get_custom_parameters($toolproxy, $tool, $params, $settings)); + } + } } return $custom; @@ -1454,6 +1471,10 @@ function lti_get_type_type_config($id) { $type->lti_coursevisible = $config['coursevisible']; } + if (isset($config['contentitem'])) { + $type->lti_contentitem = $config['contentitem']; + } + if (isset($config['debuglaunch'])) { $type->lti_debuglaunch = $config['debuglaunch']; } @@ -1489,6 +1510,10 @@ function lti_prepare_type_for_save($type, $config) { $type->forcessl = !empty($config->lti_forcessl) ? $config->lti_forcessl : 0; $config->lti_forcessl = $type->forcessl; + if (isset($config->lti_contentitem)) { + $type->contentitem = !empty($config->lti_contentitem) ? $config->lti_contentitem : 0; + $config->lti_contentitem = $type->contentitem; + } $type->timemodified = time(); @@ -2147,6 +2172,7 @@ function lti_get_capabilities() { $capabilities = array( 'basic-lti-launch-request' => '', + 'ContentItemSelectionRequest' => '', 'Context.id' => 'context_id', 'CourseSection.title' => 'context_title', 'CourseSection.label' => 'context_label', diff --git a/mod/lti/mod_form.js b/mod/lti/mod_form.js index 7318c275b8a..5095b95e292 100644 --- a/mod/lti/mod_form.js +++ b/mod/lti/mod_form.js @@ -19,11 +19,26 @@ * @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; @@ -31,6 +46,13 @@ 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; @@ -49,6 +71,21 @@ 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 typeSelector = Y.one('#id_typeid'); typeSelector.on('change', function(e){ updateToolMatches(); @@ -492,6 +529,69 @@ } } }); + }, + + 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); + } + }, + + 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'); } }; diff --git a/mod/lti/mod_form.php b/mod/lti/mod_form.php index 87819d4e728..a75498cc47c 100644 --- a/mod/lti/mod_form.php +++ b/mod/lti/mod_form.php @@ -43,6 +43,8 @@ * @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 */ @@ -54,11 +56,12 @@ 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; + global $DB, $PAGE, $OUTPUT, $USER, $COURSE, $sesskey, $section; 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; @@ -95,7 +98,12 @@ class mod_lti_mod_form extends moodleform_mod { $mform->addHelpButton('showdescriptionlaunch', 'display_description', 'lti'); // Tool settings. - $tooltypes = $mform->addElement('select', 'typeid', get_string('external_tool_type', 'lti'), array()); + $attributes = array(); + if ($update = optional_param('update', false, PARAM_INT)) { + $attributes['disabled'] = 'disabled'; + } + $attributes['class'] = 'lti_contentitem'; + $tooltypes = $mform->addElement('select', 'typeid', get_string('external_tool_type', 'lti'), array(), $attributes); $typeid = optional_param('typeid', false, PARAM_INT); $mform->getElement('typeid')->setValue($typeid); $mform->addHelpButton('typeid', 'external_tool_type', 'lti'); @@ -123,6 +131,15 @@ class mod_lti_mod_form extends moodleform_mod { } else { $attributes = array(); } + 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); + } + } $tooltypes->addOption($type->name, $id, $attributes); } @@ -247,7 +264,8 @@ class mod_lti_mod_form extends moodleform_mod { array('tooltypedeleted', 'lti'), array('tooltypenotdeleted', 'lti'), array('tooltypeupdated', 'lti'), - array('forced_help', 'lti') + array('forced_help', 'lti'), + array('configure_item', 'lti') ), ); diff --git a/mod/lti/service/toolproxy/classes/local/resource/toolproxy.php b/mod/lti/service/toolproxy/classes/local/resource/toolproxy.php index 71050f4f413..9f146e291dc 100644 --- a/mod/lti/service/toolproxy/classes/local/resource/toolproxy.php +++ b/mod/lti/service/toolproxy/classes/local/resource/toolproxy.php @@ -150,7 +150,8 @@ class toolproxy extends \mod_lti\local\ltiservice\resource_base { if ($ok) { $resources = $toolproxyjson->tool_profile->resource_handler; foreach ($resources as $resource) { - $found = false; + $launchable = false; + $messages = array(); $tool = new \stdClass(); $iconinfo = null; @@ -164,19 +165,17 @@ class toolproxy extends \mod_lti\local\ltiservice\resource_base { } foreach ($resource->message as $message) { - if ($message->message_type == 'basic-lti-launch-request') { - $found = true; - $tool->path = $message->path; - $tool->enabled_capability = $message->enabled_capability; - $tool->parameter = $message->parameter; - break; + if (($message->message_type === 'basic-lti-launch-request') || + ($message->message_type === 'ContentItemSelectionRequest')) { + $launchable = $launchable || ($message->message_type === 'basic-lti-launch-request'); + $messages[$message->message_type] = $message; } } - if (!$found) { + if (!$launchable) { continue; } - $tool->name = $resource->resource_name->default_value; + $tool->messages = $messages; $tools[] = $tool; } $ok = count($tools) > 0; @@ -196,17 +195,27 @@ class toolproxy extends \mod_lti\local\ltiservice\resource_base { $securebaseurl = $toolproxyjson->tool_profile->base_url_choice[0]->secure_base_url; } foreach ($tools as $tool) { + $messages = $tool->messages; $config = new \stdClass(); - $config->lti_toolurl = "{$baseurl}{$tool->path}"; + $config->lti_toolurl = "{$baseurl}{$messages['basic-lti-launch-request']->path}"; $config->lti_typename = $tool->name; $config->lti_coursevisible = 1; $config->lti_forcessl = 0; + if (isset($messages['ContentItemSelectionRequest'])) { + $config->lti_contentitem = 1; + if ($messages['basic-lti-launch-request']->path !== $messages['ContentItemSelectionRequest']->path) { + $config->lti_toolurl_ContentItemSelectionRequest = + "{$baseurl}{$messages['ContentItemSelectionRequest']->path}"; + } + $config->lti_enabledcapability_ContentItemSelectionRequest = implode("\n", $messages['ContentItemSelectionRequest']->enabled_capability); + $config->lti_parameter_ContentItemSelectionRequest = self::lti_extract_parameters($messages['ContentItemSelectionRequest']->parameter); + } $type = new \stdClass(); $type->state = LTI_TOOL_STATE_PENDING; $type->toolproxyid = $toolproxy->id; - $type->enabledcapability = implode("\n", $tool->enabled_capability); - $type->parameter = self::lti_extract_parameters($tool->parameter); + $type->enabledcapability = implode("\n", $messages['basic-lti-launch-request']->enabled_capability); + $type->parameter = self::lti_extract_parameters($messages['basic-lti-launch-request']->parameter); if (!empty($tool->iconpath)) { $type->icon = "{$baseurl}{$tool->iconpath}"; diff --git a/mod/lti/styles.css b/mod/lti/styles.css index 66a23e0f6d5..c2e9cbe20c3 100644 --- a/mod/lti/styles.css +++ b/mod/lti/styles.css @@ -380,3 +380,21 @@ 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;}