MDL-20534 lti: Updating privacy settings dynamically based on matched Launch URL.
[moodle.git] / mod / lti / mod_form.js
CommitLineData
61eb12d4
CS
1// This file is part of Moodle - http://moodle.org/
2//
3// Moodle is free software: you can redistribute it and/or modify
4// it under the terms of the GNU General Public License as published by
5// the Free Software Foundation, either version 3 of the License, or
6// (at your option) any later version.
7//
8// Moodle is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
15
16/**
8f45215d 17 * Javascript extensions for the External Tool activity editor.
61eb12d4
CS
18 *
19 * @package mod
20 * @subpackage lti
8f45215d 21 * @copyright Copyright (c) 2011 Moodlerooms Inc. (http://www.moodlerooms.com)
61eb12d4
CS
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
6831c7cd
CS
24(function(){
25 var Y;
e27cb316 26
6831c7cd
CS
27 M.mod_lti = M.mod_lti || {};
28
4c598b13
CS
29 M.mod_lti.LTI_SETTING_NEVER = 0;
30 M.mod_lti.LTI_SETTING_ALWAYS = 1;
31 M.mod_lti.LTI_SETTING_DELEGATE = 2;
32
6831c7cd
CS
33 M.mod_lti.editor = {
34 init: function(yui3, settings){
35 if(yui3){
36 Y = yui3;
996b0fd9 37 }
e27cb316 38
16e8f130 39 var self = this;
6831c7cd
CS
40 this.settings = Y.JSON.parse(settings);
41
42 this.urlCache = {};
43
44 this.addOptGroups();
45
d8d04121
CS
46 var updateToolMatches = function(){
47 self.updateAutomaticToolMatch(Y.one('#id_toolurl'));
48 self.updateAutomaticToolMatch(Y.one('#id_securetoolurl'));
49 };
50
6831c7cd
CS
51 var typeSelector = Y.one('#id_typeid');
52 typeSelector.on('change', function(e){
d8d04121 53 updateToolMatches();
e27cb316 54
6831c7cd
CS
55 self.toggleEditButtons();
56 });
57
58 this.createTypeEditorButtons();
59
60 this.toggleEditButtons();
e27cb316 61
6831c7cd
CS
62 var textAreas = new Y.NodeList([
63 Y.one('#id_toolurl'),
d8d04121 64 Y.one('#id_securetoolurl'),
6831c7cd
CS
65 Y.one('#id_resourcekey'),
66 Y.one('#id_password')
67 ]);
e27cb316 68
6831c7cd
CS
69 var debounce;
70 textAreas.on('keyup', function(e){
71 clearTimeout(debounce);
72
73 //If no more changes within 2 seconds, look up the matching tool URL
74 debounce = setTimeout(function(){
d8d04121 75 updateToolMatches();
6831c7cd
CS
76 }, 2000);
77 });
e27cb316 78
d8d04121 79 updateToolMatches();
6831c7cd
CS
80 },
81
16e8f130
CS
82 clearToolCache: function(){
83 this.urlCache = {};
84 },
85
d8d04121 86 updateAutomaticToolMatch: function(field){
16e8f130 87 var self = this;
e27cb316 88
d8d04121 89 var toolurl = field;
606ab1a1 90 var typeSelector = Y.one('#id_typeid');
e27cb316 91
d8d04121
CS
92 var id = field.get('id') + '_lti_automatch_tool';
93 var automatchToolDisplay = Y.one('#' + id);
6831c7cd
CS
94
95 if(!automatchToolDisplay){
96 automatchToolDisplay = Y.Node.create('<span />')
d8d04121 97 .set('id', id)
6831c7cd 98 .setStyle('padding-left', '1em');
e27cb316 99
6831c7cd
CS
100 toolurl.insert(automatchToolDisplay, 'after');
101 }
102
103 var url = toolurl.get('value');
996b0fd9 104
16e8f130
CS
105 //Hide the display if the url box is empty
106 if(!url){
6831c7cd 107 automatchToolDisplay.setStyle('display', 'none');
16e8f130
CS
108 } else {
109 automatchToolDisplay.set('innerHTML', '');
110 automatchToolDisplay.setStyle('display', '');
111 }
112
113 var selectedToolType = typeSelector.get('value');
89b7bbe9 114 var selectedOption = typeSelector.one('option[value="' + selectedToolType + '"]');
16e8f130 115
89b7bbe9 116 //A specific tool type is selected (not "auto")
4c598b13 117 //We still need to check with the server to get privacy settings
16e8f130
CS
118 if(selectedToolType > 0){
119 //If the entered domain matches the domain of the tool configuration...
120 var domainRegex = /(?:https?:\/\/)?(?:www\.)?([^\/]+)(?:\/|$)/i;
121 var match = domainRegex.exec(url);
122 if(match && match[1] && match[1].toLowerCase() === selectedOption.getAttribute('domain').toLowerCase()){
123 automatchToolDisplay.set('innerHTML', '<img style="vertical-align:text-bottom" src="' + self.settings.green_check_icon_url + '" />' + M.str.lti.using_tool_configuration + selectedOption.get('text'));
124 } else {
125 //The entered URL does not match the domain of the tool configuration
126 automatchToolDisplay.set('innerHTML', '<img style="vertical-align:text-bottom" src="' + self.settings.warning_icon_url + '" />' + M.str.lti.domain_mismatch);
127 }
6831c7cd
CS
128 }
129
130 var key = Y.one('#id_resourcekey');
131 var secret = Y.one('#id_password');
132
4c598b13
CS
133 //Indicate the tool is manually configured
134 //We still check the Launch URL with the server as course/site tools may override privacy settings
6831c7cd 135 if(key.get('value') !== '' && secret.get('value') !== ''){
16e8f130 136 automatchToolDisplay.set('innerHTML', '<img style="vertical-align:text-bottom" src="' + self.settings.green_check_icon_url + '" />' + M.str.lti.custom_config);
4c598b13
CS
137 }
138
139 var continuation = function(toolInfo){
140 self.updatePrivacySettings(toolInfo);
e27cb316 141
4c598b13
CS
142 if(toolInfo.toolname){
143 automatchToolDisplay.set('innerHTML', '<img style="vertical-align:text-bottom" src="' + self.settings.green_check_icon_url + '" />' + M.str.lti.using_tool_configuration + toolInfo.toolname);
6831c7cd 144 } else {
4c598b13
CS
145 //Inform them custom configuration is in use
146 if(key.get('value') === '' || secret.get('value') === ''){
147 automatchToolDisplay.set('innerHTML', '<img style="vertical-align:text-bottom" src="' + self.settings.warning_icon_url + '" />' + M.str.lti.tool_config_not_found);
148 }
149 }
150 };
151
152 //Cache urls which have already been checked to increase performance
153 if(self.urlCache[url]){
154 continuation(self.urlCache[url]);
155 } else {
156 self.findToolByUrl(url, function(toolInfo){
157 if(toolInfo){
6831c7cd
CS
158 self.urlCache[url] = toolInfo;
159
160 continuation(toolInfo);
4c598b13
CS
161 }
162 });
163 }
164 },
165
166 /**
167 * Updates display of privacy settings to show course / site tool configuration settings.
168 */
169 updatePrivacySettings: function(toolInfo){
170 if(!toolInfo || !toolInfo.toolname){
171 toolInfo = {
172 sendname: M.mod_lti.LTI_SETTING_DELEGATE,
173 sendemailaddr: M.mod_lti.LTI_SETTING_DELEGATE,
174 acceptgrades: M.mod_lti.LTI_SETTING_DELEGATE,
175 allowroster: M.mod_lti.LTI_SETTING_DELEGATE
176 }
177 }
178
179 var setting, control;
180
181 //Can't look these up by ID as they seem to get random IDs.
182 //Setting an id manually from mod_form made them turn into text boxes.
183 var privacyControls = {
184 sendname: Y.one('input[name=instructorchoicesendname]'),
185 sendemailaddr: Y.one('input[name=instructorchoicesendemailaddr]'),
186 acceptgrades: Y.one('input[name=instructorchoiceacceptgrades]'),
187 allowroster: Y.one('input[name=instructorchoiceallowroster]')
188 };
189
190 //Store a copy of user entered privacy settings as we may overwrite them
191 if(!this.userPrivacySettings){
192 this.userPrivacySettings = {};
193 }
194
195 for(setting in privacyControls){
196 if(privacyControls.hasOwnProperty(setting)){
197 control = privacyControls[setting];
198
199 //Only store the value if it hasn't been forced by the editor
200 if(!control.get('disabled')){
201 this.userPrivacySettings[setting] = control.get('checked');
202 }
203 }
204 }
205
206 //Update UI based on course / site tool configuration
207 for(setting in privacyControls){
208 if(privacyControls.hasOwnProperty(setting)){
209 var settingValue = toolInfo[setting];
210 control = privacyControls[setting];
211
212 if(settingValue == M.mod_lti.LTI_SETTING_NEVER){
213 control.set('disabled', true);
214 control.set('checked', false);
215 control.set('title', M.str.lti.forced_help);
216 } else if(settingValue == M.mod_lti.LTI_SETTING_ALWAYS){
217 control.set('disabled', true);
218 control.set('checked', true);
219 control.set('title', M.str.lti.forced_help);
220 } else if(settingValue == M.mod_lti.LTI_SETTING_DELEGATE){
221 control.set('disabled', false);
222
223 //Get the value out of the stored copy
224 control.set('checked', this.userPrivacySettings[setting]);
225 control.set('title', '');
226 }
6831c7cd
CS
227 }
228 }
229 },
230
231 getSelectedToolTypeOption: function(){
232 var typeSelector = Y.one('#id_typeid');
233
89b7bbe9 234 return typeSelector.one('option[value="' + typeSelector.get('value') + '"]');
6831c7cd
CS
235 },
236
237 /**
238 * Separate tool listing into option groups. Server-side select control
239 * doesn't seem to support this.
240 */
241 addOptGroups: function(){
242 var typeSelector = Y.one('#id_typeid');
243
244 if(typeSelector.one('option[courseTool=1]')){
245 //One ore more course tools exist
246
247 var globalGroup = Y.Node.create('<optgroup />')
248 .set('id', 'global_tool_group')
249 .set('label', M.str.lti.global_tool_types);
250
251 var courseGroup = Y.Node.create('<optgroup />')
252 .set('id', 'course_tool_group')
253 .set('label', M.str.lti.course_tool_types);
254
606ab1a1 255 var globalOptions = typeSelector.all('option[globalTool=1]').remove().each(function(node){
6831c7cd
CS
256 globalGroup.append(node);
257 });
258
606ab1a1 259 var courseOptions = typeSelector.all('option[courseTool=1]').remove().each(function(node){
6831c7cd 260 courseGroup.append(node);
996b0fd9 261 });
6831c7cd 262
606ab1a1
CS
263 if(globalOptions.size() > 0){
264 typeSelector.append(globalGroup);
265 }
e27cb316 266
606ab1a1
CS
267 if(courseOptions.size() > 0){
268 typeSelector.append(courseGroup);
269 }
996b0fd9 270 }
6831c7cd
CS
271 },
272
273 /**
274 * Adds buttons for creating, editing, and deleting tool types.
275 * Javascript is a requirement to edit course level tools at this point.
276 */
277 createTypeEditorButtons: function(){
16e8f130 278 var self = this;
e27cb316 279
6831c7cd
CS
280 var typeSelector = Y.one('#id_typeid');
281
282 var createIcon = function(id, tooltip, iconUrl){
e27cb316 283 return Y.Node.create('<a />')
6831c7cd
CS
284 .set('id', id)
285 .set('title', tooltip)
286 .setStyle('margin-left', '.5em')
287 .set('href', 'javascript:void(0);')
288 .append(Y.Node.create('<img src="' + iconUrl + '" />'));
289 }
290
291 var addIcon = createIcon('lti_add_tool_type', M.str.lti.addtype, this.settings.add_icon_url);
292 var editIcon = createIcon('lti_edit_tool_type', M.str.lti.edittype, this.settings.edit_icon_url);
293 var deleteIcon = createIcon('lti_delete_tool_type', M.str.lti.deletetype, this.settings.delete_icon_url);
294
295 editIcon.on('click', function(e){
296 var toolTypeId = typeSelector.get('value');
297
298 if(self.getSelectedToolTypeOption().getAttribute('editable')){
299 window.open(self.settings.instructor_tool_type_edit_url + '&action=edit&typeid=' + toolTypeId, 'edit_tool');
300 } else {
301 alert(M.str.lti.cannot_edit);
302 }
303 });
304
305 addIcon.on('click', function(e){
306 window.open(self.settings.instructor_tool_type_edit_url + '&action=add', 'add_tool');
307 });
308
309 deleteIcon.on('click', function(e){
310 var toolTypeId = typeSelector.get('value');
311
312 if(self.getSelectedToolTypeOption().getAttribute('editable')){
313 if(confirm(M.str.lti.delete_confirmation)){
16e8f130 314 self.deleteTool(toolTypeId);
6831c7cd
CS
315 }
316 } else {
317 alert(M.str.lti.cannot_delete);
318 }
319 });
320
321 typeSelector.insert(addIcon, 'after');
322 addIcon.insert(editIcon, 'after');
323 editIcon.insert(deleteIcon, 'after');
324 },
325
326 toggleEditButtons: function(){
327 var lti_edit_tool_type = Y.one('#lti_edit_tool_type');
328 var lti_delete_tool_type = Y.one('#lti_delete_tool_type');
329
330 //Make the edit / delete icons look enabled / disabled.
331 //Does not work in older browsers, but alerts will catch those cases.
332 if(this.getSelectedToolTypeOption().getAttribute('editable')){
333 lti_edit_tool_type.setStyle('opacity', '1');
334 lti_delete_tool_type.setStyle('opacity', '1');
335 } else {
336 lti_edit_tool_type.setStyle('opacity', '.2');
337 lti_delete_tool_type.setStyle('opacity', '.2');
338 }
339 },
340
16e8f130 341 addToolType: function(toolType){
6831c7cd
CS
342 var typeSelector = Y.one('#id_typeid');
343 var course_tool_group = Y.one('#course_tool_group');
344
345 var option = Y.Node.create('<option />')
16e8f130
CS
346 .set('text', toolType.name)
347 .set('value', toolType.id)
6831c7cd
CS
348 .set('selected', 'selected')
349 .setAttribute('editable', '1')
16e8f130
CS
350 .setAttribute('courseTool', '1')
351 .setAttribute('domain', toolType.tooldomain);
6831c7cd
CS
352
353 if(course_tool_group){
354 course_tool_group.append(option);
355 } else {
356 typeSelector.append(option);
357 }
e27cb316 358
16e8f130
CS
359 //Adding the new tool may affect which tool gets matched automatically
360 this.clearToolCache();
89b7bbe9
CS
361 this.updateAutomaticToolMatch(Y.one('#id_toolurl'));
362 this.updateAutomaticToolMatch(Y.one('#id_securetoolurl'));
6831c7cd
CS
363 },
364
16e8f130 365 updateToolType: function(toolType){
6831c7cd
CS
366 var typeSelector = Y.one('#id_typeid');
367
89b7bbe9 368 var option = typeSelector.one('option[value="' + toolType.id + '"]');
16e8f130
CS
369 option.set('text', toolType.name)
370 .set('domain', toolType.tooldomain);
e27cb316 371
16e8f130
CS
372 //Editing the tool may affect which tool gets matched automatically
373 this.clearToolCache();
89b7bbe9
CS
374 this.updateAutomaticToolMatch(Y.one('#id_toolurl'));
375 this.updateAutomaticToolMatch(Y.one('#id_securetoolurl'));
16e8f130
CS
376 },
377
378 deleteTool: function(toolTypeId){
379 var self = this;
e27cb316 380
16e8f130
CS
381 Y.io(self.settings.instructor_tool_type_edit_url + '&action=delete&typeid=' + toolTypeId, {
382 on: {
383 success: function(){
384 self.getSelectedToolTypeOption().remove();
e27cb316 385
16e8f130
CS
386 //Editing the tool may affect which tool gets matched automatically
387 self.clearToolCache();
89b7bbe9
CS
388 self.updateAutomaticToolMatch(Y.one('#id_toolurl'));
389 self.updateAutomaticToolMatch(Y.one('#id_securetoolurl'));
16e8f130
CS
390 },
391 failure: function(){
392
393 }
394 }
395 });
6831c7cd
CS
396 },
397
398 findToolByUrl: function(url, callback){
16e8f130 399 var self = this;
4c598b13
CS
400
401 if(!url || url === ''){
402 return callback();
403 }
e27cb316
CS
404
405 Y.io(self.settings.ajax_url, {
16e8f130 406 data: {action: 'find_tool_config',
6831c7cd
CS
407 course: self.settings.courseId,
408 toolurl: url
409 },
996b0fd9 410
6831c7cd
CS
411 on: {
412 success: function(transactionid, xhr){
413 var response = xhr.response;
e27cb316 414
6831c7cd 415 var toolInfo = Y.JSON.parse(response);
e27cb316 416
6831c7cd
CS
417 callback(toolInfo);
418 },
419 failure: function(){
996b0fd9 420
6831c7cd
CS
421 }
422 }
423 });
424 }
996b0fd9 425
6831c7cd 426 };
61eb12d4 427})();