Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
/**
17
 * Encapsules the behavior for creating a tool type and tool proxy from a
18
 * registration url in Moodle.
19
 *
20
 * Manages the UI while operations are occuring, including rendering external
21
 * registration page within the iframe.
22
 *
23
 * See template: mod_lti/external_registration
24
 *
25
 * @module     mod_lti/external_registration
26
 * @copyright  2015 Ryan Wyllie <ryan@moodle.com>
27
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
28
 * @since      3.1
29
 */
30
define(['jquery', 'core/ajax', 'core/notification', 'core/templates', 'mod_lti/events',
31
        'mod_lti/tool_proxy', 'mod_lti/tool_type', 'mod_lti/keys', 'core/str'],
32
        function($, ajax, notification, templates, ltiEvents, toolProxy, toolType, KEYS, str) {
33
 
34
    var SELECTORS = {
35
        EXTERNAL_REGISTRATION_CONTAINER: '#external-registration-page-container',
36
        EXTERNAL_REGISTRATION_TEMPLATE_CONTAINER: '#external-registration-template-container',
37
        EXTERNAL_REGISTRATION_CANCEL_BUTTON: '#cancel-external-registration',
38
        TOOL_TYPE_CAPABILITIES_CONTAINER: '#tool-type-capabilities-container',
39
        TOOL_TYPE_CAPABILITIES_TEMPLATE_CONTAINER: '#tool-type-capabilities-template-container',
40
        CAPABILITIES_AGREE_CONTAINER: '.capabilities-container',
41
    };
42
 
43
    /**
44
     * Return the external registration cancel button element. This button is
45
     * the cancel button that appears while the iframe is rendered.
46
     *
47
     * @method getExternalRegistrationCancelButton
48
     * @private
49
     * @return {JQuery} jQuery object
50
     */
51
    var getExternalRegistrationCancelButton = function() {
52
        return $(SELECTORS.EXTERNAL_REGISTRATION_CANCEL_BUTTON);
53
    };
54
 
55
    /**
56
     * Return the container that holds all elements for the external registration, including
57
     * the cancel button and the iframe.
58
     *
59
     * @method getExternalRegistrationContainer
60
     * @private
61
     * @return {JQuery} jQuery object
62
     */
63
    var getExternalRegistrationContainer = function() {
64
        return $(SELECTORS.EXTERNAL_REGISTRATION_CONTAINER);
65
    };
66
 
67
    /**
68
     * Return the container that holds the external registration page template. It should
69
     * be the iframe.
70
     *
71
     * @method getExternalRegistrationTemplateContainer
72
     * @private
73
     * @return {JQuery} jQuery object
74
     */
75
    var getExternalRegistrationTemplateContainer = function() {
76
        return $(SELECTORS.EXTERNAL_REGISTRATION_TEMPLATE_CONTAINER);
77
    };
78
 
79
    /**
80
     * Return the container that holds the elements for displaying the list of capabilities
81
     * that this tool type requires. This container wraps the loading indicator and the template
82
     * container.
83
     *
84
     * @method getToolTypeCapabilitiesContainer
85
     * @private
86
     * @return {JQuery} jQuery object
87
     */
88
    var getToolTypeCapabilitiesContainer = function() {
89
        return $(SELECTORS.TOOL_TYPE_CAPABILITIES_CONTAINER);
90
    };
91
 
92
    /**
93
     * Return the container that holds the template that lists the capabilities that the
94
     * tool type will require.
95
     *
96
     * @method getToolTypeCapabilitiesTemplateContainer
97
     * @private
98
     * @return {JQuery} jQuery object
99
     */
100
    var getToolTypeCapabilitiesTemplateContainer = function() {
101
        return $(SELECTORS.TOOL_TYPE_CAPABILITIES_TEMPLATE_CONTAINER);
102
    };
103
 
104
    /**
105
     * Triggers a visual indicator to show that the capabilities section is loading.
106
     *
107
     * @method startLoadingCapabilitiesContainer
108
     * @private
109
     */
110
    var startLoadingCapabilitiesContainer = function() {
111
        getToolTypeCapabilitiesContainer().addClass('loading');
112
    };
113
 
114
    /**
115
     * Removes the visual indicator that shows the capabilities section is loading.
116
     *
117
     * @method stopLoadingCapabilitiesContainer
118
     * @private
119
     */
120
    var stopLoadingCapabilitiesContainer = function() {
121
        getToolTypeCapabilitiesContainer().removeClass('loading');
122
    };
123
 
124
    /**
125
     * Adds a visual indicator that shows the cancel button is loading.
126
     *
127
     * @method startLoadingCancel
128
     * @private
129
     */
130
    var startLoadingCancel = function() {
131
        getExternalRegistrationCancelButton().addClass('loading');
132
    };
133
 
134
    /**
135
     * Adds a visual indicator that shows the cancel button is loading.
136
     *
137
     * @method startLoadingCancel
138
     * @private
139
     */
140
    var stopLoadingCancel = function() {
141
        getExternalRegistrationCancelButton().removeClass('loading');
142
    };
143
 
144
    /**
145
     * Stops displaying the tool type capabilities container.
146
     *
147
     * @method hideToolTypeCapabilitiesContainer
148
     * @private
149
     */
150
    var hideToolTypeCapabilitiesContainer = function() {
151
        getToolTypeCapabilitiesContainer().addClass('hidden');
152
    };
153
 
154
    /**
155
     * Displays the tool type capabilities container.
156
     *
157
     * @method showToolTypeCapabilitiesContainer
158
     * @private
159
     */
160
    var showToolTypeCapabilitiesContainer = function() {
161
        getToolTypeCapabilitiesContainer().removeClass('hidden');
162
    };
163
 
164
    /**
165
     * Stops displaying the external registration content.
166
     *
167
     * @method hideExternalRegistrationContent
168
     * @private
169
     */
170
    var hideExternalRegistrationContent = function() {
171
        getExternalRegistrationContainer().addClass('hidden');
172
    };
173
 
174
    /**
175
     * Displays the external registration content.
176
     *
177
     * @method showExternalRegistrationContent
178
     * @private
179
     */
180
    var showExternalRegistrationContent = function() {
181
        getExternalRegistrationContainer().removeClass('hidden');
182
    };
183
 
184
    /**
185
     * Save the given tool proxy id on the DOM.
186
     *
187
     * @method setToolProxyId
188
     * @private
189
     * @param {Integer} id Tool proxy ID
190
     */
191
    var setToolProxyId = function(id) {
192
        var button = getExternalRegistrationCancelButton();
193
        button.attr('data-tool-proxy-id', id);
194
    };
195
 
196
    /**
197
     * Return the saved tool proxy id.
198
     *
199
     * @method getToolProxyId
200
     * @private
201
     * @return {String} Tool proxy ID
202
     */
203
    var getToolProxyId = function() {
204
        var button = getExternalRegistrationCancelButton();
205
        return button.attr('data-tool-proxy-id');
206
    };
207
 
208
    /**
209
     * Remove the saved tool proxy id.
210
     *
211
     * @method clearToolProxyId
212
     * @private
213
     */
214
    var clearToolProxyId = function() {
215
        var button = getExternalRegistrationCancelButton();
216
        button.removeAttr('data-tool-proxy-id');
217
    };
218
 
219
    /**
220
     * Returns true if a tool proxy id has been recorded.
221
     *
222
     * @method hasToolProxyId
223
     * @private
224
     * @return {Boolean}
225
     */
226
    var hasToolProxyId = function() {
227
        return getToolProxyId() ? true : false;
228
    };
229
 
230
    /**
231
     * Checks if this process has created a tool proxy within
232
     * Moodle yet.
233
     *
234
     * @method hasCreatedToolProxy
235
     * @private
236
     * @return {Boolean}
237
     */
238
    var hasCreatedToolProxy = function() {
239
        var button = getExternalRegistrationCancelButton();
240
        return button.attr('data-tool-proxy-new') && hasToolProxyId();
241
    };
242
 
243
    /**
244
     * Records that this process has created a tool proxy.
245
     *
246
     * @method setProxyAsNew
247
     * @private
248
     * @return {Boolean}
249
     */
250
    var setProxyAsNew = function() {
251
        var button = getExternalRegistrationCancelButton();
252
        return button.attr('data-tool-proxy-new', "new");
253
    };
254
 
255
    /**
256
     * Records that this process has not created a tool proxy.
257
     *
258
     * @method setProxyAsOld
259
     * @private
260
     * @return {Boolean}
261
     */
262
    var setProxyAsOld = function() {
263
        var button = getExternalRegistrationCancelButton();
264
        return button.removeAttr('data-tool-proxy-new');
265
    };
266
 
267
    /**
268
     * Gets the external registration request required to be sent to the external
269
     * registration page using a form.
270
     *
271
     * See mod_lti/tool_proxy_registration_form template.
272
     *
273
     * @method getRegistrationRequest
274
     * @private
275
     * @param {Integer} id Tool Proxy ID
276
     * @return {Promise} jQuery Deferred object
277
     */
278
    var getRegistrationRequest = function(id) {
279
        var request = {
280
            methodname: 'mod_lti_get_tool_proxy_registration_request',
281
            args: {
282
                id: id
283
            }
284
        };
285
 
286
        return ajax.call([request])[0];
287
    };
288
 
289
    /**
290
     * Cancel an in progress external registration. This will perform any necessary
291
     * clean up of tool proxies and return the page section back to the home section.
292
     *
293
     * @method cancelRegistration
294
     * @private
295
     * @return {Promise} jQuery Deferred object
296
     */
297
    var cancelRegistration = function() {
298
        startLoadingCancel();
299
        var promise = $.Deferred();
300
 
301
        // If we've created a proxy as part of this process then
302
        // we need to delete it to clean up the data in the back end.
303
        if (hasCreatedToolProxy()) {
304
            var id = getToolProxyId();
305
            toolProxy.delete(id).done(function() {
306
                promise.resolve();
307
            }).fail(function(failure) {
308
                promise.reject(failure);
309
            });
310
        } else {
311
            promise.resolve();
312
        }
313
 
314
        promise.done(function() {
315
            // Return to the original page.
316
            finishExternalRegistration();
317
            stopLoadingCancel();
318
        }).fail(function(failure) {
319
            notification.exception(failure);
320
            finishExternalRegistration();
321
            stopLoadingCancel();
322
            str.get_string('failedtodeletetoolproxy', 'mod_lti').done(function(s) {
323
                var feedback = {
324
                    message: s,
325
                    error: true
326
                };
327
                $(document).trigger(ltiEvents.REGISTRATION_FEEDBACK, feedback);
328
            }).fail(notification.exception);
329
        });
330
 
331
        return promise;
332
    };
333
 
334
    /**
335
     * Load the external registration template and render it in the DOM and display it.
336
     *
337
     * @method renderExternalRegistrationWindow
338
     * @private
339
     * @param {Object} registrationRequest
340
     * @return {Promise} jQuery Deferred object
341
     */
342
    var renderExternalRegistrationWindow = function(registrationRequest) {
343
        var promise = templates.render('mod_lti/tool_proxy_registration_form', registrationRequest);
344
 
345
        promise.done(function(html, js) {
346
            // Show the external registration page in an iframe.
347
            var container = getExternalRegistrationTemplateContainer();
348
            container.append(html);
349
            templates.runTemplateJS(js);
350
 
351
            container.find('form').submit();
352
            showExternalRegistrationContent();
353
        }).fail(notification.exception);
354
 
355
        return promise;
356
    };
357
 
358
    /**
359
     * Send a request to Moodle server to set the state of the tool type to configured (active).
360
     *
361
     * @method setTypeStatusActive
362
     * @private
363
     * @param {Object} typeData A set of data representing a type, as returned by a request to get a type
364
     *               from the Moodle server.
365
     * @return {Promise} jQuery Deferred object
366
     */
367
    var setTypeStatusActive = function(typeData) {
368
        return toolType.update({
369
            id: typeData.id,
370
            state: toolType.constants.state.configured
371
        });
372
    };
373
 
374
    /**
375
     * Render and display an agreement page for the user to acknowledge the list of capabilities
376
     * (groups of data) that the external tool requires in order to work. If the user agrees then
377
     * we will activate the tool so that it is immediately available. If they don't agree then
378
     * the tool remains in a pending state within Moodle until agreement is given.
379
     *
380
     * @method promptForToolTypeCapabilitiesAgreement
381
     * @private
382
     * @param {Object} typeData A set of data representing a type, as returned by a request to get a type
383
     *               from the Moodle server.
384
     * @return {Promise} jQuery Deferred object
385
     */
386
    var promptForToolTypeCapabilitiesAgreement = function(typeData) {
387
        var promise = $.Deferred();
388
 
389
        templates.render('mod_lti/tool_type_capabilities_agree', typeData).done(function(html, js) {
390
            var container = getToolTypeCapabilitiesTemplateContainer();
391
 
392
            hideExternalRegistrationContent();
393
            showToolTypeCapabilitiesContainer();
394
 
395
            templates.replaceNodeContents(container, html, js);
396
 
397
            var choiceContainer = container.find(SELECTORS.CAPABILITIES_AGREE_CONTAINER);
398
 
399
            // The user agrees to allow the tool to use the groups of data so we can go
400
            // ahead and activate it for them so that it can be used straight away.
401
            choiceContainer.on(ltiEvents.CAPABILITIES_AGREE, function() {
402
                startLoadingCapabilitiesContainer();
403
                setTypeStatusActive(typeData).always(function() {
404
                    stopLoadingCapabilitiesContainer();
405
                    container.empty();
406
                    promise.resolve();
407
                });
408
            });
409
 
410
            // The user declines to let the tool use the data. In this case we leave
411
            // the tool as pending and they can delete it using the main screen if they
412
            // wish.
413
            choiceContainer.on(ltiEvents.CAPABILITIES_DECLINE, function() {
414
                container.empty();
415
                promise.resolve();
416
            });
417
        }).fail(promise.reject);
418
 
419
        promise.done(function() {
420
            hideToolTypeCapabilitiesContainer();
421
        }).fail(notification.exception);
422
 
423
        return promise;
424
    };
425
 
426
    /**
427
     * Send a request to the Moodle server to create a tool proxy using the registration URL the user
428
     * has provided. The proxy is required for the external registration page to work correctly.
429
     *
430
     * After the proxy is created the external registration page is rendered within an iframe for the user
431
     * to complete the registration in the external page.
432
     *
433
     * If the tool proxy creation fails then we redirect the page section back to the home section and
434
     * display the error, rather than rendering the external registration page.
435
     *
436
     * @method createAndRegisterToolProxy
437
     * @private
438
     * @param {String} url Tool registration URL to register
439
     * @return {Promise} jQuery Deferred object
440
     */
441
    var createAndRegisterToolProxy = function(url) {
442
        var promise = $.Deferred();
443
 
444
        if (!url || url === "") {
445
            // No URL has been input so do nothing.
446
            promise.resolve();
447
        } else {
448
            // A tool proxy needs to exist before the external page is rendered because
449
            // the external page sends requests back to Moodle for information that is stored
450
            // in the proxy.
451
            toolProxy.create({regurl: url})
452
                .done(function(result) {
453
                        // Note that it's a new proxy so we will always clean it up.
454
                        setProxyAsNew();
455
                        promise = registerProxy(result.id);
456
                    })
457
                .fail(function(exception) {
458
                        // Clean up.
459
                        cancelRegistration();
460
                        // Let the user know what the error is.
461
                        var feedback = {
462
                            message: exception.message,
463
                            error: true
464
                        };
465
                        $(document).trigger(ltiEvents.REGISTRATION_FEEDBACK, feedback);
466
                        promise.reject(exception);
467
                    });
468
        }
469
 
470
        return promise;
471
    };
472
 
473
    /**
474
     * Loads the window to register a proxy, given an ID.
475
     *
476
     * @method registerProxy
477
     * @private
478
     * @param {Integer} id Proxy id to register
479
     * @return {Promise} jQuery Deferred object to fail or resolve
480
     */
481
    var registerProxy = function(id) {
482
        var promise = $.Deferred();
483
        // Save the id on the DOM to cleanup later.
484
        setToolProxyId(id);
485
 
486
        // There is a specific set of data needed to send to the external registration page
487
        // in a form, so let's get it from our server.
488
        getRegistrationRequest(id)
489
            .done(function(registrationRequest) {
490
                    renderExternalRegistrationWindow(registrationRequest)
491
                        .done(function() {
492
                                promise.resolve();
493
                            })
494
                        .fail(promise.fail);
495
                })
496
            .fail(promise.fail);
497
 
498
        return promise;
499
    };
500
 
501
    /**
502
     * Complete the registration process, clean up any left over data and
503
     * trigger the appropriate events.
504
     *
505
     * @method finishExternalRegistration
506
     * @private
507
     */
508
    var finishExternalRegistration = function() {
509
        if (hasToolProxyId()) {
510
            clearToolProxyId();
511
        }
512
        setProxyAsOld(false);
513
 
514
        hideExternalRegistrationContent();
515
        var container = getExternalRegistrationTemplateContainer();
516
        container.empty();
517
 
518
        $(document).trigger(ltiEvents.STOP_EXTERNAL_REGISTRATION);
519
    };
520
 
521
    /**
522
     * Sets up the listeners for user interaction on the page.
523
     *
524
     * @method registerEventListeners
525
     * @private
526
     */
527
    var registerEventListeners = function() {
528
 
529
        $(document).on(ltiEvents.START_EXTERNAL_REGISTRATION, function(event, data) {
530
                if (!data) {
531
                    return;
532
                }
533
                if (data.url) {
534
                    createAndRegisterToolProxy(data.url);
535
                }
536
                if (data.proxyid) {
537
                    registerProxy(data.proxyid);
538
                }
539
            });
540
 
541
        var cancelExternalRegistrationButton = getExternalRegistrationCancelButton();
542
        cancelExternalRegistrationButton.click(function(e) {
543
            e.preventDefault();
544
            cancelRegistration();
545
        });
546
        cancelExternalRegistrationButton.keypress(function(e) {
547
            if (!e.metaKey && !e.shiftKey && !e.altKey && !e.ctrlKey) {
548
                if (e.keyCode == KEYS.ENTER || e.keyCode == KEYS.SPACE) {
549
                    e.preventDefault();
550
                    cancelRegistration();
551
                }
552
            }
553
        });
554
 
555
        // This is gross but necessary due to isolated jQuery scopes between
556
        // child iframe and parent windows. There is no other way to communicate.
557
        //
558
        // This function gets called by the moodle page that received the redirect
559
        // from the external registration page and handles the external page's returned
560
        // parameters.
561
        //
562
        // See AMD module mod_lti/external_registration_return.
563
        window.triggerExternalRegistrationComplete = function(data) {
564
            var promise = $.Deferred();
565
            var feedback = {
566
                message: "",
567
                error: false
568
            };
569
 
570
            if (data.status == "success") {
571
                str.get_string('successfullycreatedtooltype', 'mod_lti').done(function(s) {
572
                    feedback.message = s;
573
                }).fail(notification.exception);
574
 
575
                // Trigger appropriate events when we've completed the necessary requests.
576
                promise.done(function() {
577
                    finishExternalRegistration();
578
                    $(document).trigger(ltiEvents.REGISTRATION_FEEDBACK, feedback);
579
                    $(document).trigger(ltiEvents.NEW_TOOL_TYPE);
580
                }).fail(notification.exception);
581
 
582
                // We should have created a tool proxy by this point.
583
                if (hasCreatedToolProxy()) {
584
                    var proxyId = getToolProxyId();
585
 
586
                    // We need the list of types that are linked to this proxy. We're assuming it'll
587
                    // only be one because this process creates a one-to-one type->proxy.
588
                    toolType.getFromToolProxyId(proxyId).done(function(types) {
589
                        if (types && types.length) {
590
                            // There should only be one result.
591
                            var typeData = types[0];
592
 
593
                            // Check if the external tool required access to any Moodle data (users, courses etc).
594
                            if (typeData.hascapabilitygroups) {
595
                                // If it did then we ask the user to agree to those groups before the type is
596
                                // activated (i.e. can be used in Moodle).
597
                                promptForToolTypeCapabilitiesAgreement(typeData).always(function() {
598
                                    promise.resolve();
599
                                });
600
                            } else {
601
                                promise.resolve();
602
                            }
603
                        } else {
604
                            promise.resolve();
605
                        }
606
                    }).fail(function() {
607
                        promise.resolve();
608
                    });
609
                }
610
            } else {
611
                // Anything other than success is failure.
612
                feedback.message = data.error;
613
                feedback.error = true;
614
 
615
                // Cancel registration to clean up any proxies and tools that were
616
                // created.
617
                promise.done(function() {
618
                    cancelRegistration().always(function() {
619
                        $(document).trigger(ltiEvents.REGISTRATION_FEEDBACK, feedback);
620
                    });
621
                }).fail(notification.exception);
622
 
623
                promise.resolve();
624
            }
625
 
626
            return promise;
627
        };
628
    };
629
 
630
    return {
631
 
632
        /**
633
         * Initialise this module.
634
         */
635
        init: function() {
636
            registerEventListeners();
637
        }
638
    };
639
});