From 916ddca1daf73cfb96aefa1ddab373bcf2252adf Mon Sep 17 00:00:00 2001 From: "Dave Shepard (CDH)" Date: Fri, 2 Nov 2018 11:07:34 -0700 Subject: [PATCH] CCLE-6956 - LTI tools in rich text editor --- lib/editor/atto/plugins/lti/contentitem.php | 61 ++++ .../atto/plugins/lti/contentitem_return.php | 62 ++++ .../atto/plugins/lti/lang/en/atto_lti.php | 21 ++ lib/editor/atto/plugins/lti/lib.php | 60 ++++ lib/editor/atto/plugins/lti/version.php | 31 ++ lib/editor/atto/plugins/lti/view.php | 68 ++++ .../plugins/lti/yui/src/button/build.json | 11 + .../plugins/lti/yui/src/button/js/button.js | 135 ++++++++ .../yui/src/button/js/deeplinkplacement.js | 312 ++++++++++++++++++ .../lti/yui/src/button/meta/button.json | 7 + .../ucla/config/shared_prod_moodle-config.php | 4 +- mod/lti/db/install.xml | 7 + mod/lti/db/upgrade.php | 27 +- mod/lti/edit_form.php | 29 ++ mod/lti/lang/en/lti.php | 14 + mod/lti/launch.php | 9 +- mod/lti/locallib.php | 152 ++++++++- mod/lti/version.php | 6 +- 18 files changed, 1000 insertions(+), 16 deletions(-) create mode 100644 lib/editor/atto/plugins/lti/contentitem.php create mode 100644 lib/editor/atto/plugins/lti/contentitem_return.php create mode 100644 lib/editor/atto/plugins/lti/lang/en/atto_lti.php create mode 100644 lib/editor/atto/plugins/lti/lib.php create mode 100644 lib/editor/atto/plugins/lti/version.php create mode 100644 lib/editor/atto/plugins/lti/view.php create mode 100644 lib/editor/atto/plugins/lti/yui/src/button/build.json create mode 100644 lib/editor/atto/plugins/lti/yui/src/button/js/button.js create mode 100644 lib/editor/atto/plugins/lti/yui/src/button/js/deeplinkplacement.js create mode 100644 lib/editor/atto/plugins/lti/yui/src/button/meta/button.json diff --git a/lib/editor/atto/plugins/lti/contentitem.php b/lib/editor/atto/plugins/lti/contentitem.php new file mode 100644 index 00000000000..3820cde01fd --- /dev/null +++ b/lib/editor/atto/plugins/lti/contentitem.php @@ -0,0 +1,61 @@ +. + +/** + * 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'); + +$id = required_param('id', PARAM_INT); +$courseid = required_param('course', PARAM_INT); +$callback = required_param('callback', 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); + +// 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(), + 'callback' => $callback, +]; +$returnurl = new \moodle_url('/lib/editor/atto/plugins/lti/contentitem_return.php', $returnurlparams); + +// Prepare the request. +$request = lti_build_content_item_selection_request( + $id, $course, $returnurl, '', '', [], [], + false, false, false, false, false, 'richtexteditor' +); + +// Get the launch HTML. +$content = lti_post_launch_html($request->params, $request->url, false); + +echo $content; + + diff --git a/lib/editor/atto/plugins/lti/contentitem_return.php b/lib/editor/atto/plugins/lti/contentitem_return.php new file mode 100644 index 00000000000..f99a7f3e68e --- /dev/null +++ b/lib/editor/atto/plugins/lti/contentitem_return.php @@ -0,0 +1,62 @@ +. + +/** + * @package atto_lti + * @copyright 2018 The Regents of the University of California + * @license GPL v3 + */ + + + +require_once(dirname(__FILE__) . '/../../../../../config.php'); + +$courseid = required_param('course', PARAM_INT); + +$callback = required_param('callback', PARAM_ALPHANUMEXT); +$contentitemsraw = required_param('content_items', PARAM_RAW_TRIMMED); + +require_login($courseid); + +$context = context_course::instance($courseid); +require_capability('moodle/course:manageactivities', $context); +require_capability('mod/lti:addcoursetool', $context); + +$contentitems = json_decode($contentitemsraw); + +$errors = []; + +// Affirm that the content item is a JSON object. +if (!is_object($contentitems) && !is_array($contentitems)) { + $errors[] = 'invalidjson'; +} + + + +?> + + diff --git a/lib/editor/atto/plugins/lti/lang/en/atto_lti.php b/lib/editor/atto/plugins/lti/lang/en/atto_lti.php new file mode 100644 index 00000000000..495423d3c15 --- /dev/null +++ b/lib/editor/atto/plugins/lti/lang/en/atto_lti.php @@ -0,0 +1,21 @@ +. + +$string['pluginname'] = 'LTI'; + +$string['erroroccurred'] = 'An error occurred adding this content'; +$string['lti'] = 'lti'; +$string['privacy:metadata'] = 'The atto_lti plugin does not store any personal data'; diff --git a/lib/editor/atto/plugins/lti/lib.php b/lib/editor/atto/plugins/lti/lib.php new file mode 100644 index 00000000000..fdbf3c47846 --- /dev/null +++ b/lib/editor/atto/plugins/lti/lib.php @@ -0,0 +1,60 @@ +. +// + +/** + * This file contains library functions for the Atto LTI plugin + * + * @package atto_lti + * @copyright 2018 The Regents of the University of California + * @author David Shepard + * @license Not figured out + */ + + +defined('MOODLE_INTERNAL') || die(); + + +function atto_lti_strings_for_js () { + global $PAGE; + $PAGE->requires->strings_for_js(['lti', 'erroroccurred'], 'atto_lti'); +} + + +function atto_lti_params_for_js ($elementid, $options, $fpoptions) { + global $PAGE; + + $ltitooltypes = lti_load_type_by_placement('richtexteditorplugin'); + + $tooltypes = []; + foreach ($ltitooltypes as $type) { + $type->config = lti_get_config( + (object)[ + 'typeid' => $type->id, + ] + ); + $tooltypes[] = $type; + } + + return [ + 'toolTypes' => $tooltypes, + 'course' => $PAGE->course, + 'resourcebase' => sha1( + $PAGE->url->__toString() . '&' . $PAGE->course->sortorder + . '&' . $PAGE->course->timecreated + ), + ]; +} diff --git a/lib/editor/atto/plugins/lti/version.php b/lib/editor/atto/plugins/lti/version.php new file mode 100644 index 00000000000..6075843dc4e --- /dev/null +++ b/lib/editor/atto/plugins/lti/version.php @@ -0,0 +1,31 @@ +. + +/** + * + * Atto text editor LTI integration + * + * @package atto_lti + * @copyright The Regents of the UC + * @license Test + */ + + +defined('MOODLE_INTERNAL') || die(); + +$plugin->version = 2018041003; +$plugin->requires = 2017110800; +$plugin->component = 'atto_lti'; diff --git a/lib/editor/atto/plugins/lti/view.php b/lib/editor/atto/plugins/lti/view.php new file mode 100644 index 00000000000..e62420a6834 --- /dev/null +++ b/lib/editor/atto/plugins/lti/view.php @@ -0,0 +1,68 @@ +. +// + +/** + * This file launches LTI-enabled tools that do not have a course module. + * + * If a tool instance is added to the rich text editor, it should not also show + * up in the list of course activities. This file passes through the + * resource_link_id without checking the database for it. + * + * @package atto_lti + * @copyright 2018 The Regents of the University of California + * @author David Shepard + * @license Not figured out + */ + +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); +$resourcelinkid = required_param('resourcelinkid', PARAM_ALPHANUMEXT); +$ltitypeid = required_param('ltitypeid', PARAM_INT); +$contenturl = optional_param('contenturl', '', PARAM_RAW); +$customdata = optional_param('custom', '', PARAM_RAW); + + +$course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST); +$context = context_course::instance($courseid); + +$ltitype = $DB->get_record('lti_types', ['id' => $ltitypeid]); + +require_login($course); + +require_capability('mod/lti:view', $context); + + +$lti = new stdClass(); + +$lti->id = $resourcelinkid; +$lti->typeid = $ltitypeid; +$lti->launchcontainer = LTI_LAUNCH_CONTAINER_WINDOW; +$lti->toolurl = $contenturl; +$lti->custom = new stdClass(); +$lti->instructorcustomparameters = []; +$lti->debuglaunch = false; +if ($customdata) { + $decoded = json_decode($customdata, true); + foreach ($decoded as $key => $value) { + $lti->custom->$key = $value; + } +} + +lti_launch_tool($lti, 'richtexteditor'); diff --git a/lib/editor/atto/plugins/lti/yui/src/button/build.json b/lib/editor/atto/plugins/lti/yui/src/button/build.json new file mode 100644 index 00000000000..3c9b1102b63 --- /dev/null +++ b/lib/editor/atto/plugins/lti/yui/src/button/build.json @@ -0,0 +1,11 @@ +{ + "name": "moodle-atto_lti-button", + "builds" : { + "moodle-atto_lti-button" : { + "jsfiles" : [ + "button.js", + "deeplinkplacement.js" + ] + } + } +} diff --git a/lib/editor/atto/plugins/lti/yui/src/button/js/button.js b/lib/editor/atto/plugins/lti/yui/src/button/js/button.js new file mode 100644 index 00000000000..9447d098b74 --- /dev/null +++ b/lib/editor/atto/plugins/lti/yui/src/button/js/button.js @@ -0,0 +1,135 @@ +// 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 . + +/** + * @package atto_lti + * @copyright 2018 The Regents of the University of California + * @license GPL v3 + */ + + +/** + * @module moodle-atto_lti-button + */ + + +/** + * Atto text editor LTI activities plugin + * + * @namespace M.atto_lti + * @class Button + * @extends M.editor_atto.EditorPlugin + */ + + +require(['core/str'], function (str) { + + var errorMessage = null, + stringPromise = str.get_string('erroroccurred', 'atto_lti'); + + $.when(stringPromise).done(function (invalid) { + errorMessage = invalid; + }); + + document.CALLBACKS = { + handleError: function (errors) { + alert(errorMessage); + for (var i = 0; i < errors.length; i++) { + console.error(errors[i]); + } + } + }; + +}); + +Y.namespace('M.atto_lti').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], { + + _CREATEACTIVITYURL: '/lib/editor/atto/plugins/lti/view.php', + _CONTENT_ITEM_SELECTION_URL: '/lib/editor/atto/plugins/lti/contentitem.php', + + _panel: null, + + _addTool: function (event, tool) { + event.preventDefault(); + var resourceLinkId = this._createResourceLinkId(), + host = this.get('host'), + panel, + courseid = this._course; + + document.CALLBACKS['f' + resourceLinkId] = function (contentItemData) { + if (!contentItemData) { + return; + } + + for (var i = 0; i < contentItemData['@graph'].length; i++) { + var item = contentItemData['@graph'][i]; + var strategyFactory = new Y.M.atto_lti.PlacementStrategyFactory(); + var strategy = strategyFactory.strategyFor(item, courseid, resourceLinkId, tool); + var render = strategy.toHtml; + host.insertContentAtFocusPoint(render(item)); + } + host.saveSelection(); + host.updateOriginal(); + panel.hide(); + }; + + this._panel = new M.core.dialogue({ + bodyContent: '', + headerContent: tool.name, + width: '67%', + height: '66%', + draggable: false, + visible: true, + zindex: 100, + modal: true, + focusOnPreviousTargetAfterHide: true, + render: true + }); + + panel = this._panel; + + }, + + initializer: function (arg) { + this._course = arg.course; + + this._createResourceLinkId = (function (base) { + return function () { + return base + '_' + (new Date()).getTime(); + }; + }(arg.resourcebase)); + this.addToolbarMenu({ + + icon: '/theme/image.php/uclashared/casa/1527094325/icon', + + globalItemConfig:{ + callback: this._addTool + }, + + items: arg.toolTypes.map(function (arg) { + return { + text : arg.name, + callbackArgs: arg + }; + }) + }); + + } + +}); diff --git a/lib/editor/atto/plugins/lti/yui/src/button/js/deeplinkplacement.js b/lib/editor/atto/plugins/lti/yui/src/button/js/deeplinkplacement.js new file mode 100644 index 00000000000..d35689e8667 --- /dev/null +++ b/lib/editor/atto/plugins/lti/yui/src/button/js/deeplinkplacement.js @@ -0,0 +1,312 @@ +// 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 . + +/** + * @package atto_lti + * @copyright 2018 The Regents of the University of California + * @license GPL v3 + */ + +/** + * @module moodle-atto_lti-button + */ + +/** + * Atto text editor LTI activities plugin + * + * @namespace M.atto_lti + * @class Button + * @extends M.editor_atto.EditorPlugin + */ + + +Y.namespace('M.atto_lti').PlacementStrategyFactory = function() { + + this.strategyFor = function(item, course, resourceLinkId, tool) { + + var strategyClass = Y.M.atto_lti.EmbeddedContentRenderingStrategy; + + if (item.mediaType === 'application/vnd.ims.lti.v1.ltilink' + || item.placementAdvice) { + strategyClass = Y.M.atto_lti.IframeRenderingStrategy; + + if (item.placementAdvice) { + + switch (item.placementAdvice.presentationDocumentTarget) { + case 'window': + case 'popup': + case 'overlay': + strategyClass = Y.M.atto_lti.PlaceholderRenderingStrategy; + break; + case 'iframe': + strategyClass = Y.M.atto_lti.IframeRenderingStrategy; + break; + case 'embed': + case 'frame': + strategyClass = Y.M.atto_lti.EmbeddedContentRenderingStrategy; + break; + default: + alert('Unsupported presentation target: ' + + item.placementAdvice.presentationDocumentTarget); + break; + } + } + } + + var strategy = new strategyClass(item, course, resourceLinkId, tool); + + return strategy; + }; +}; + +Y.namespace('M.atto_lti').EmbeddedContentRenderingStrategy = function(item, + course, resourceLinkId, tool) { + + var mimeTypePieces = item.mediaType.split('/'), + mimeTypeType = mimeTypePieces[0]; + + var TEMPLATES = { + image : Y.Handlebars.compile('{{alt}}' + ), + ltiLink : Y.Handlebars.compile('' + ); + + this.toHtml = function() { + return template({ + item : item, + custom : JSON.stringify(item.custom), + courseId : course.id, + resourcelinkid : resourceLinkId, + ltiTypeId : tool.id + }); + }; + +}; + +Y.namespace('M.atto_lti').PlaceholderRenderingStrategy = function(item) { + + Y.M.atto_lti.PlaceholderRenderingStrategy.superclass.constructor.apply( + this, arguments + ); + + var placeholder, placeholderClass, action; + + if (typeof item.thumbnail !== 'undefined') { + placeholderClass = Y.M.atto_lti.PreviewImagePlaceholderStrategy; + } else if (typeof item.icon !== 'undefined') { + placeholderClass = Y.M.atto_lti.IconPlaceholderStrategy; + } else { + placeholderClass = Y.M.atto_lti.TextPlaceholderStrategy; + } + + placeholder = new placeholderClass(item); + + switch (item.placementAdvice.presentationDocumentTarget) { + // NOTE: this may seem redundant for now, but it is likely we will want to + // extend this in the future. + case 'window': + case 'popup': + case 'overlay': + action = new Y.M.atto_lti.NewWindowTargetAction(item, placeholder); + break; + default: + alert('Unrecognized preesntation document target'); + } + + this.toHtml = function() { + return action.toHtml(); + }; + +}; + +Y.namespace('M.atto_lti').PreviewImagePlaceholderStrategy = function(item) { + + Y.M.atto_lti.PreviewImagePlaceholderStrategy.superclass.constructor.call( + this, item + ); + + var template = Y.Handlebars + .compile('{{title}}' + ); + + this.toHtml = function() { + return template({ + thumnbail : item.thumbnail, + title : item.title + }); + }; + +}; + +Y.namespace('M.atto_lti').IconPlaceholderStrategy = function(item) { + + Y.M.atto_lti.IconPlaceholderStrategy.superclass.constructor + .call(this, item); + + var template = Y.Handlebars + .compile('{{title}}' + ); + + this.toHtml = function() { + return template({ + icon : item.icon, + title : item.title + }); + }; + +}; + +Y.namespace('M.atto_lti').TextPlaceholderStrategy = function(item) { + Y.M.atto_lti.TextPlaceholderStrategy.superclass.constructor + .call(this, item); + + this.toHtml = function() { + if (item.text) { + return item.text; + } else { + return item.title; + } + }; +} + +Y.namespace('M.atto_lti').NewWindowTargetAction = function(item, placeholder) { + + var template = Y.Handlebars + .compile('{{placeholder.toHtml()}}'); + + this.toHtml = function() { + return template({ + item : item, + placeholder : placeholder + }); + }; + +}; diff --git a/lib/editor/atto/plugins/lti/yui/src/button/meta/button.json b/lib/editor/atto/plugins/lti/yui/src/button/meta/button.json new file mode 100644 index 00000000000..9c69257375f --- /dev/null +++ b/lib/editor/atto/plugins/lti/yui/src/button/meta/button.json @@ -0,0 +1,7 @@ +{ + "moodle-atto_lti-button":{ + "requires":[ + "moodle-editor_atto-plugin" + ] + } +} diff --git a/local/ucla/config/shared_prod_moodle-config.php b/local/ucla/config/shared_prod_moodle-config.php index 435af4a92fc..72532f7e0a0 100644 --- a/local/ucla/config/shared_prod_moodle-config.php +++ b/local/ucla/config/shared_prod_moodle-config.php @@ -469,7 +469,9 @@ insert2 = chemrender, chemistry, computing, equation files2 = mediagallery, panoptobutton poodll = poodll - accessibility = accessibilitychecker, accessibilityhelper'; + accessibility = accessibilitychecker, accessibilityhelper + lti = lti +'; $CFG->forced_plugin_settings['editor_atto']['autosavefrequency'] = 30; // CCLE-4849 - Number of groups displayed on first row of Atto HTML Editor diff --git a/mod/lti/db/install.xml b/mod/lti/db/install.xml index 040f32edae3..1d6c30c56e7 100644 --- a/mod/lti/db/install.xml +++ b/mod/lti/db/install.xml @@ -81,6 +81,13 @@ + + + + + + + diff --git a/mod/lti/db/upgrade.php b/mod/lti/db/upgrade.php index e3452a0a5fa..63d67f2741f 100644 --- a/mod/lti/db/upgrade.php +++ b/mod/lti/db/upgrade.php @@ -37,15 +37,14 @@ * * @package mod_lti * @copyright 2009 Marc Alier, Jordi Piguillem, Nikolas Galanis - * marc.alier@upc.edu + * marc.alier@upc.edu * @copyright 2009 Universitat Politecnica de Catalunya http://www.upc.edu * @author Marc Alier * @author Jordi Piguillem * @author Nikolas Galanis * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ - - defined('MOODLE_INTERNAL') || die; +defined('MOODLE_INTERNAL') || die(); /** * xmldb_lti_upgrade is the function that upgrades @@ -155,6 +154,28 @@ function xmldb_lti_upgrade($oldversion) { upgrade_mod_savepoint(true, 2017111301, 'lti'); } + // START UCLA MOD: CCLE-6956 - LTI Apps in Rich Text Editor. + if ($oldversion < 2017111302) { + $table = new xmldb_table('lti_types'); + + $fields = []; + $fields[] = new xmldb_field('asactivity', XMLDB_TYPE_INTEGER, 1, null, null, null, 1, 'secureicon'); + $fields[] = new xmldb_field('asassignment', XMLDB_TYPE_INTEGER, 1, null, null, null, 0, 'asactivity'); + $fields[] = new xmldb_field('assignmenturl', XMLDB_TYPE_TEXT, 255, null, false, null, null, 'asassignment'); + $fields[] = new xmldb_field('asmenulink', XMLDB_TYPE_INTEGER, 1, null, null, null, 0, 'assignmenturl'); + $fields[] = new xmldb_field('menulinkurl', XMLDB_TYPE_TEXT, 255, null, false, null, null, 'asmenulink'); + $fields[] = new xmldb_field('asrichtexteditorplugin', XMLDB_TYPE_INTEGER, 1, null, null, null, 0, 'menulinkurl'); + $fields[] = new xmldb_field('richtexteditorurl', XMLDB_TYPE_TEXT, 255, null, false, null, null, 'asrichtexteditorplugin'); + foreach ($fields as $field) { + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + } + + upgrade_mod_savepoint(true, 2017111302, 'lti'); + } + // END UCLA MOD: CCLE-6956. + return true; } diff --git a/mod/lti/edit_form.php b/mod/lti/edit_form.php index 8db9ca53c95..96d9a1027b2 100644 --- a/mod/lti/edit_form.php +++ b/mod/lti/edit_form.php @@ -147,6 +147,35 @@ public function definition() { $mform->disabledIf('lti_contentitem', null); } + // START UCLA MOD: CCLE-6956 - LTI Apps in Rich Text Editor. + $mform->addElement('text', 'lti_toolurl_ContentItemSelectionRequest', get_string('toolurl_contentitemselectionrequest', 'lti'), array( + 'size' => '64' + )); + $mform->setType('lti_toolurl_ContentItemSelectionRequest', PARAM_URL); + $mform->setAdvanced('lti_toolurl_ContentItemSelectionRequest'); + $mform->addHelpButton('lti_toolurl_ContentItemSelectionRequest', 'toolurl_contentitemselectionrequest', 'lti'); + $mform->disabledIf('lti_toolurl_ContentItemSelectionRequest', 'lti_contentitem', 'notchecked'); + if ($istool) { + $mform->disabledIf('lti_toolurl__ContentItemSelectionRequest', null); + } + + $mform->addElement('checkbox', 'lti_asassignment', get_string('placementasassignment', 'lti')); + $mform->addElement('text', 'lti_assignmenturl', get_string('placementassignmenturl', 'lti'), [ + 'size' => '64' + ]); + $mform->setType('lti_assignmenturl', PARAM_URL); + $mform->addElement('checkbox', 'lti_asmenulink', get_string('placementasmenulink', 'lti')); + $mform->addElement('text', 'lti_menulinkurl', get_string('placementmenulinkurl', 'lti'), [ + 'size' => '64' + ]); + $mform->setType('lti_menulinkurl', PARAM_URL); + $mform->addElement('checkbox', 'lti_asrichtexteditorplugin', get_string('placementasrichtexteditorplugin', 'lti')); + $mform->addElement('text', 'lti_richtexteditorurl', get_string('placementrichtexteditorurl', 'lti'), [ + 'size' => '64' + ]); + $mform->setType('lti_richtexteditorurl', PARAM_URL); + // END UCLA MOD: CCLE-6956. + $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 0d7a0aa10c2..7024a827f33 100644 --- a/mod/lti/lang/en/lti.php +++ b/mod/lti/lang/en/lti.php @@ -349,6 +349,16 @@ Tools which do not require secure communication from Moodle and do not provide additional services (such as grade reporting) may not require a shared secret.'; $string['pending'] = 'Pending'; +// START UCLA MOD: CCLE-6956 - LTI Apps in Rich Text Editor. +$string['placements'] = 'Placements'; +$string['placementactivityurl'] = 'Activity URL'; +$string['placementasassignment'] = 'As Assignment'; +$string['placementasmenulink'] = 'As Menu Link'; +$string['placementasrichtexteditorplugin'] = 'As Rich Text Editor Plugin'; +$string['placementassignmenturl'] = 'Assignment URL'; +$string['placementmenulinkurl'] = 'Menu Link URL'; +$string['placementrichtexteditorurl'] = 'Rich Text Editor URL'; +// END UCLA MOD: CCLE-6956. $string['pluginadministration'] = 'External tool administration'; $string['pluginname'] = 'External tool'; $string['preferheight'] = 'Preferred height'; @@ -511,6 +521,10 @@ $string['tooltypeupdated'] = 'Preconfigured tool updated'; $string['toolurl'] = 'Tool URL'; $string['toolurlplaceholder'] = 'Tool URL...'; +// START UCLA MOD: CCLE-6956 - LTI Apps in Rich Text Editor. +$string['toolurl_contentitemselectionrequest'] = 'Content Selection URL'; +$string['toolurl_contentitemselectionrequest_help'] = 'The Content Selection URL will be used to launch the content selection page from the tool provider. If it is empty, the Tool URL will be used'; +// END UCLA MOD: CCLE-6956. $string['toolurl_help'] = 'The tool URL is used to match tool URLs to the correct tool configuration. Prefixing the URL with http(s) is optional. Additionally, the base URL is used as the tool URL if a tool URL is not specified in the external tool instance. diff --git a/mod/lti/launch.php b/mod/lti/launch.php index 17771a7b9e3..a8c7fa1fb71 100644 --- a/mod/lti/launch.php +++ b/mod/lti/launch.php @@ -52,6 +52,9 @@ $id = required_param('id', PARAM_INT); // Course Module ID. $triggerview = optional_param('triggerview', 1, PARAM_BOOL); +// START UCLA MOD: CCLE-6956 - LTI Apps in Rich Text Editor. +$placement = optional_param('placement', 'activity', PARAM_RAW); +// END UCLA MOD: CCLE-6956. $cm = get_coursemodule_from_id('lti', $id, 0, false, MUST_EXIST); $lti = $DB->get_record('lti', array('id' => $cm->instance), '*', MUST_EXIST); @@ -77,5 +80,7 @@ } $lti->cmid = $cm->id; -lti_launch_tool($lti); - +// START UCLA MOD: CCLE-6956 - LTI Apps in Rich Text Editor. +//lti_launch_tool($lti); +lti_launch_tool($lti, $placement); +// END UCLA MOD: CCLE-6956. diff --git a/mod/lti/locallib.php b/mod/lti/locallib.php index 96f6c6965ed..bfc33159d16 100644 --- a/mod/lti/locallib.php +++ b/mod/lti/locallib.php @@ -95,7 +95,10 @@ * @return array the endpoint URL and parameters (including the signature) * @since Moodle 3.0 */ -function lti_get_launch_data($instance) { +// START UCLA MOD: CCLE-6956 - LTI Apps in Rich Text Editor. +//function lti_get_launch_data($instance) { +function lti_get_launch_data($instance, $placement = 'activity') { +// END UCLA MOD: CCLE-6956. global $PAGE, $CFG; if (empty($instance->typeid)) { @@ -160,6 +163,14 @@ function lti_get_launch_data($instance) { $endpoint = !empty($instance->toolurl) ? $instance->toolurl : $typeconfig['toolurl']; $endpoint = trim($endpoint); + // START UCLA MOD: CCLE-6956 - LTI Apps in Rich Text Editor. + if ($placement !== 'activity') { + $desiredplacement = $placement . 'url'; + if (!empty($instance->$desiredplacement)) { + $endpoint = $instance->$desiredplacement; + } + } + // END UCLA MOD: CCLE-6956. // 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)) { @@ -183,6 +194,11 @@ function lti_get_launch_data($instance) { $course = $PAGE->course; $islti2 = isset($tool->toolproxyid); + // START UCLA MOD: CCLE-6956 - LTI Apps in Rich Text Editor. + if (!property_exists($instance, 'course')) { + $instance->course = $course->id; + } + // END UCLA MOD: CCLE-6956. $allparams = lti_build_request($instance, $typeconfig, $course, $typeid, $islti2); if ($islti2) { $requestparams = lti_build_request_lti2($tool, $allparams); @@ -269,11 +285,18 @@ function lti_get_launch_data($instance) { * Launch an external tool activity. * * @param stdClass $instance the external tool activity settings + * @param string $placement the name of the placement, either 'activity', 'menulink', or 'richtexteditor' * @return string The HTML code containing the javascript code for the launch */ -function lti_launch_tool($instance) { - - list($endpoint, $parms) = lti_get_launch_data($instance); +// START UCLA MOD: CCLE-6956 - LTI Apps in Rich Text Editor. +// Variable $placement is either 'activity', 'menulink', or 'richtexteditor'. +//function lti_launch_tool($instance) { +function lti_launch_tool($instance, $placement) { +// END UCLA MOD: CCLE-6956. + // START UCLA MOD: CCLE-6956 - LTI Apps in Rich Text Editor. + //list($endpoint, $parms) = lti_get_launch_data($instance); + list($endpoint, $parms) = lti_get_launch_data($instance, $placement); + // END UCLA MOD: CCLE-6956. $debuglaunch = ( $instance->debuglaunch == 1 ); $content = lti_post_launch_html($parms, $endpoint, $debuglaunch); @@ -460,6 +483,13 @@ function lti_build_request($instance, $typeconfig, $course, $typeid = null, $isl ) { $requestparams['lis_person_contact_email_primary'] = $USER->email; } + // START UCLA MOD: CCLE-6956 - LTI Apps in Rich Text Editor. + if (property_exists($instance, 'custom')) { + foreach ($instance->custom as $key => $value) { + $requestparams['custom_' . $key] = $value; + } + } + // END UCLA MOD: CCLE-6956. return $requestparams; } @@ -616,7 +646,10 @@ function lti_build_custom_parameters($toolproxy, $tool, $instance, $params, $cus */ 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) { + // START UCLA MOD: CCLE-6956 - LTI Apps in Rich Text Editor. + //$unsigned = false, $canconfirm = false, $copyadvice = false) { + $unsigned = false, $canconfirm = false, $copyadvice = false, $placement = 'activity') { + // END UCLA MOD: CCLE-6956. $tool = lti_get_type($id); // Validate parameters. if (!$tool) { @@ -628,6 +661,15 @@ function lti_build_content_item_selection_request($id, $course, moodle_url $retu if (!is_array($presentationtargets)) { throw new coding_exception('The list of accepted presentation targets should be in an array'); } + // START UCLA MOD: CCLE-6956 - LTI Apps in Rich Text Editor. + if (!in_array($placement, ['activity', 'assignment', 'menulink', 'richtexteditor',])) { + throw new Moodle_Exception("Invalid placement type: $placement"); + } + // END UCLA MOD: CCLE-6956. + + if (!in_array($placement, ['activity', 'assignment', 'menulink', 'richtexteditor', ])) { + throw new Moodle_Exception("Invalid placement type: $placement"); + } // Check title. If empty, use the tool's name. if (empty($title)) { @@ -663,10 +705,28 @@ function lti_build_content_item_selection_request($id, $course, moodle_url $retu } // Set the tool URL. + $placementurlkey = $placement . 'url'; if (!empty($typeconfig['toolurl_ContentItemSelectionRequest'])) { - $toolurl = new moodle_url($typeconfig['toolurl_ContentItemSelectionRequest']); + // START UCLA MOD: CCLE-6956 - LTI Apps in Rich Text Editor. + //$toolurl = new moodle_url($typeconfig['toolurl_ContentItemSelectionRequest']); + // Check tool overrides. + $placementurlkey = $placement . 'url'; + if (!empty($typeconfig[$placementurlkey])) { + $toolurl = new moodle_url($typeconfig[$placement.'url']); + } else { + $toolurl = new moodle_url($typeconfig['toolurl_ContentItemSelectionRequest']); + } + // END UCLA MOD: CCLE-6956. } else { - $toolurl = new moodle_url($typeconfig['toolurl']); + // START UCLA MOD: CCLE-6956 - LTI Apps in Rich Text Editor. + //$toolurl = new moodle_url($typeconfig['toolurl']); + // Check tool overrides. + if (!empty($typeconfig[$placementurlkey])) { + $toolurl = new moodle_url($typeconfig[$placement.'url']); + } else { + $toolurl = new moodle_url($typeconfig['toolurl']); + } + // END UCLA MOD: CCLE-6956. } // Check if SSL is forced. @@ -695,7 +755,14 @@ function lti_build_content_item_selection_request($id, $course, moodle_url $retu // 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'); + // START UCLA MOD: CCLE-6956 - LTI Apps in Rich Text Editor. + //$standardparams = lti_build_standard_request(null, $orgid, $islti2, 'ContentItemSelectionRequest'); + $requesttype = 'ContentItemSelectionRequest'; + if (!$typeconfig['contentitem']) { + $requesttype = 'basic-lti-launch-request'; + } + $standardparams = lti_build_standard_request(null, $orgid, $islti2, $requesttype); + // END UCLA MOD: CCLE-6956. $requestparams = array_merge($requestparams, $standardparams); // Get custom request parameters and merge to the request parameters. @@ -730,6 +797,9 @@ function lti_build_content_item_selection_request($id, $course, moodle_url $retu 'frame', 'iframe', 'window', + // START UCLA MOD: CCLE-6956 - LTI Apps in Rich Text Editor. + 'embed', + // END UCLA MOD: CCLE-6956. ]; } $requestparams['accept_presentation_document_targets'] = implode(',', $presentationtargets); @@ -1786,6 +1856,15 @@ function lti_get_type_type_config($id) { $type->lti_secureicon = $basicltitype->secureicon; + // START UCLA MOD: CCLE-6956 - LTI Apps in Rich Text Editor. + $type->lti_asassignment = $basicltitype->asassignment; + $type->lti_asmenulink = $basicltitype->asmenulink; + $type->lti_asrichtexteditorplugin = $basicltitype->asrichtexteditorplugin; + $type->lti_assignmenturl = $basicltitype->assignmenturl; + $type->lti_menulinkurl = $basicltitype->menulinkurl; + $type->lti_richtexteditorurl = $basicltitype->richtexteditorurl; + // END UCLA MOD: CCLE-6956. + if (isset($config['resourcekey'])) { $type->lti_resourcekey = $config['resourcekey']; } @@ -1855,6 +1934,12 @@ function lti_get_type_type_config($id) { $type->lti_module_class_type = $config['module_class_type']; } + // START UCLA MOD: CCLE-6956 - LTI Apps in Rich Text Editor. + if (isset($config['toolurl_ContentItemSelectionRequest'])) { + $type->lti_toolurl_ContentItemSelectionRequest = $config['toolurl_ContentItemSelectionRequest']; + } + // END UCLA MOD: CCLE-6956. + return $type; } @@ -1887,6 +1972,35 @@ function lti_prepare_type_for_save($type, $config) { $config->lti_contentitem = $type->contentitem; } + // START UCLA MOD: CCLE-6956 - LTI Apps in Rich Text Editor. + if (isset($config->lti_toolurl_ContentItemSelectionRequest)) { + if (!empty($config->lti_toolurl_ContentItemSelectionRequest)) { + $type->toolurl_ContentItemSelectionRequest = $config->lti_toolurl_ContentItemSelectionRequest; + } else { + $type->toolurl_ContentItemSelectionRequest = ''; + } + $config->lti_toolurl_ContentItemSelectionRequest = $type->toolurl_ContentItemSelectionRequest; + } + + $type->asassignment = false; + $type->asmenulink = false; + $type->asrichtexteditorplugin = false; + + if (isset($config->lti_asassignment)) { + $type->asassignment = $config->lti_asassignment; + } + if (isset($config->lti_asmenulink)) { + $type->asmenulink = $config->lti_asmenulink; + } + if (isset($config->lti_asrichtexteditorplugin)) { + $type->asrichtexteditorplugin = $config->lti_asrichtexteditorplugin; + } + + $type->assignmenturl = $config->lti_assignmenturl; + $type->menulinkurl = $config->lti_menulinkurl; + $type->richtexteditorurl = $config->lti_richtexteditorurl; + // END UCLA MOD: CCLE-6956. + $type->timemodified = time(); unset ($config->lti_typename); @@ -1934,6 +2048,28 @@ function lti_update_type($type, $config) { } } +// START UCLA MOD: CCLE-6956 - LTI Apps in Rich Text Editor. +/** + * Get all types that can be placed in a specific placement + * + * @param string $placementname, one of 'assignment', 'activity', 'menulink', or 'richtexteditorplugin' + * + * @return array arry of tools + */ +function lti_load_type_by_placement (string $placementname) { + global $DB; + + $queryfield = [ + 'assignment' => 'asassignment', + 'activity' => 'asactivity', + 'menulink' => 'asmenulink', + 'richtexteditorplugin' => 'asrichtexteditorplugin', + ][$placementname]; + + return $DB->get_records('lti_types', [$queryfield => 1], 'name'); +} +// END UCLA MOD: CCLE-6956. + function lti_add_type($type, $config) { global $USER, $SITE, $DB; diff --git a/mod/lti/version.php b/mod/lti/version.php index a871ec1612a..8746ac3dd7a 100644 --- a/mod/lti/version.php +++ b/mod/lti/version.php @@ -47,8 +47,10 @@ */ defined('MOODLE_INTERNAL') || die; - -$plugin->version = 2017111301; // The current module version (Date: YYYYMMDDXX). +// START UCLA MOD: CCLE-6956 - LTI Apps in Rich Text Editor. +//$plugin->version = 2017111301; // The current module version (Date: YYYYMMDDXX). +$plugin->version = 2018110200; // The current module version (Date: YYYYMMDDXX). +// END UCLA MOD: CCLE-6956. $plugin->requires = 2017110800; // Requires this Moodle version. $plugin->component = 'mod_lti'; // Full name of the plugin (used for diagnostics). $plugin->cron = 0;