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
 * @package    atto_h5p
18
 * @copyright  2019 Bas Brands  <bas@moodle.com>
19
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
20
 */
21
 
22
/**
23
 * @module moodle-atto_h5p-button
24
 */
25
 
26
/**
27
 * Atto h5p content tool.
28
 *
29
 * @namespace M.atto_h5p
30
 * @class Button
31
 * @extends M.editor_atto.EditorPlugin
32
 */
33
 
34
var CSS = {
35
        CONTENTWARNING: 'att_h5p_contentwarning',
36
        H5PBROWSER: 'openh5pbrowser',
37
        INPUTALT: 'atto_h5p_altentry',
38
        INPUTH5PFILE: 'atto_h5p_file',
39
        INPUTSUBMIT: 'atto_h5p_urlentrysubmit',
40
        OPTION_DOWNLOAD_BUTTON: 'atto_h5p_option_download_button',
41
        OPTION_COPYRIGHT_BUTTON: 'atto_h5p_option_copyright_button',
42
        OPTION_EMBED_BUTTON: 'atto_h5p_option_embed_button',
43
        URLWARNING: 'atto_h5p_warning'
44
    },
45
    SELECTORS = {
46
        CONTENTWARNING: '.' + CSS.CONTENTWARNING,
47
        H5PBROWSER: '.' + CSS.H5PBROWSER,
48
        INPUTH5PFILE: '.' + CSS.INPUTH5PFILE,
49
        INPUTSUBMIT: '.' + CSS.INPUTSUBMIT,
50
        OPTION_DOWNLOAD_BUTTON: '.' + CSS.OPTION_DOWNLOAD_BUTTON,
51
        OPTION_COPYRIGHT_BUTTON: '.' + CSS.OPTION_COPYRIGHT_BUTTON,
52
        OPTION_EMBED_BUTTON: '.' + CSS.OPTION_EMBED_BUTTON,
53
        URLWARNING: '.' + CSS.URLWARNING
54
    },
55
 
56
    COMPONENTNAME = 'atto_h5p',
57
 
58
    TEMPLATE = '' +
59
            '<form class="atto_form mform" id="{{elementid}}_atto_h5p_form">' +
60
                '<div style="display:none" role="alert" class="alert alert-warning mb-1 {{CSS.CONTENTWARNING}}">' +
61
                    '{{get_string "noh5pcontent" component}}' +
62
                '</div>' +
63
                '<div style="display:none" role="alert" class="alert alert-warning mb-1 {{CSS.URLWARNING}}">' +
64
                    '{{get_string "invalidh5purl" component}}' +
65
                '</div>' +
66
                '{{#if canUploadAndEmbed}}' +
67
                    '<div class="mt-2 mb-4 attoh5pinstructions">{{{get_string "instructions" component}}}</div>' +
68
                '{{/if}}' +
69
                '<div class="mb-4">' +
70
                    '<label for="{{elementid}}_{{CSS.H5PBROWSER}}">' +
71
                        '{{#if canUploadAndEmbed}}' +
72
                            '{{get_string "h5pfileorurl" component}}' +
73
                        '{{/if}}' +
74
                        '{{^if canUploadAndEmbed}}' +
75
                            '{{#if canUpload}}' +
76
                                '{{get_string "h5pfile" component}}' +
77
                            '{{/if}}' +
78
                            '{{#if canEmbed}}' +
79
                                '{{get_string "h5purl" component}}' +
80
                            '{{/if}}' +
81
                        '{{/if}}' +
82
                    '</label>' +
83
                    '<div class="input-group input-append w-100">' +
84
                        '<input class="form-control {{CSS.INPUTH5PFILE}}" type="url" value="{{fileURL}}" ' +
85
                        'id="{{elementid}}_{{CSS.INPUTH5PFILE}}" data-region="h5pfile" size="32"/>' +
86
                        '{{#if canUpload}}' +
87
                            '<span class="input-group-append">' +
88
                                '<button class="btn btn-secondary {{CSS.H5PBROWSER}}" type="button">' +
89
                                '{{get_string "browserepositories" component}}</button>' +
90
                            '</span>' +
91
                        '{{/if}}' +
92
                    '</div>' +
93
                    '{{#if canUpload}}' +
94
                        '<fieldset class="mt-2 collapsible" id="{{elementid}}_h5poptions">' +
95
                            '<legend class="d-flex align-items-center px-1">' +
96
                                '<div class="position-relative d-flex ftoggler align-items-center position-relative mr-1">' +
97
                                    '<a role="button" data-toggle="collapse" href="#h5poptions"' +
98
                                    'aria-expanded="{{#if showOptions}}true{{/if}}{{^if showOptions}}false{{/if}}"' +
99
                                        'aria-controls="h5poptions"' +
100
                                        'class="btn btn-icon mr-1 icons-collapse-expand stretched-link fheader collapsed">' +
101
                                        '<span class="expanded-icon icon-no-margin p-2"' +
102
                                            'title="{{get_string "collapse" "moodle"}}">' +
103
                                            '<i class="icon fa fa-chevron-down fa-fw " aria-hidden="true"></i>' +
104
                                        '</span>' +
105
                                        '<span class="collapsed-icon icon-no-margin p-2"' +
106
                                            'title="{{get_string "expand" "moodle"}}">' +
107
                                            '<span class="dir-rtl-hide">' +
108
                                                '<i class="icon fa fa-chevron-right fa-fw " aria-hidden="true"></i>' +
109
                                            '</span>' +
110
                                            '<span class="dir-ltr-hide">' +
111
                                                '<i class="icon fa fa-chevron-left fa-fw " aria-hidden="true"></i>' +
112
                                            '</span>' +
113
                                        '</span>' +
114
                                        '<span class="sr-only">{{get_string "h5poptions" component}}</span>' +
115
                                    '</a>' +
116
                                    '<h3 class="d-flex align-self-stretch align-items-center mb-0" aria-hidden="true">' +
117
                                        '{{get_string "h5poptions" component}}' +
118
                                    '</h3>' +
119
                                '</div>' +
120
                            '</legend>' +
121
                            '<div id="h5poptions" class="fcontainer collapseable collapse px-1 {{#if showOptions}}show{{/if}}">' +
122
                                '<div class="form-check">' +
123
                                    '<input type="checkbox" {{optionDownloadButton}} ' +
124
                                    'class="form-check-input {{CSS.OPTION_DOWNLOAD_BUTTON}}"' +
125
                                    'aria-label="{{get_string "downloadbutton" component}}" ' +
126
                                    'id="{{elementid}}_h5p-option-allow-download"/>' +
127
                                    '<label class="form-check-label" for="{{elementid}}_h5p-option-allow-download">' +
128
                                    '{{get_string "downloadbutton" component}}' +
129
                                    '</label>' +
130
                                '</div>' +
131
                                '<div class="form-check">' +
132
                                    '<input type="checkbox" {{optionEmbedButton}} ' +
133
                                    'class="form-check-input {{CSS.OPTION_EMBED_BUTTON}}" ' +
134
                                    'aria-label="{{get_string "embedbutton" component}}" ' +
135
                                        'id="{{elementid}}_h5p-option-embed-button"/>' +
136
                                    '<label class="form-check-label" for="{{elementid}}_h5p-option-embed-button">' +
137
                                    '{{get_string "embedbutton" component}}' +
138
                                    '</label>' +
139
                                '</div>' +
140
                                '<div class="form-check mb-2">' +
141
                                    '<input type="checkbox" {{optionCopyrightButton}} ' +
142
                                    'class="form-check-input {{CSS.OPTION_COPYRIGHT_BUTTON}}" ' +
143
                                    'aria-label="{{get_string "copyrightbutton" component}}" ' +
144
                                        'id="{{elementid}}_h5p-option-copyright-button"/>' +
145
                                    '<label class="form-check-label" for="{{elementid}}_h5p-option-copyright-button">' +
146
                                    '{{get_string "copyrightbutton" component}}' +
147
                                    '</label>' +
148
                                '</div>' +
149
                            '</div>' +
150
                        '</fieldset>' +
151
                    '{{/if}}' +
152
                '</div>' +
153
                '<div class="text-center">' +
154
                '<button class="btn btn-secondary {{CSS.INPUTSUBMIT}}" type="submit">' + '' +
155
                    '{{get_string "pluginname" component}}</button>' +
156
                '</div>' +
157
            '</form>',
158
 
159
        H5PTEMPLATE = '' +
160
            '{{#if addParagraphs}}<p><br></p>{{/if}}' +
161
            '<div class="h5p-placeholder" contenteditable="false">' +
162
                '{{{url}}}' +
163
            '</div>' +
164
            '{{#if addParagraphs}}<p><br></p>{{/if}}';
165
 
166
Y.namespace('M.atto_h5p').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
167
    /**
168
     * A reference to the current selection at the time that the dialogue
169
     * was opened.
170
     *
171
     * @property _currentSelection
172
     * @type Range
173
     * @private
174
     */
175
    _currentSelection: null,
176
 
177
    /**
178
     * A reference to the currently open form.
179
     *
180
     * @param _form
181
     * @type Node
182
     * @private
183
     */
184
    _form: null,
185
 
186
    /**
187
     * A reference to the currently selected H5P div.
188
     *
189
     * @param _form
190
     * @type Node
191
     * @private
192
     */
193
    _H5PDiv: null,
194
 
195
    /**
196
     * Allowed methods of adding H5P.
197
     *
198
     * @param _allowedmethods
199
     * @type String
200
     * @private
201
     */
202
    _allowedmethods: 'none',
203
 
204
    initializer: function() {
205
        this._allowedmethods = this.get('allowedmethods');
206
        if (this._allowedmethods === 'none') {
207
            // Plugin not available here.
208
            return;
209
        }
210
        this.addButton({
211
            icon: 'icon',
212
            iconComponent: 'atto_h5p',
213
            callback: this._displayDialogue,
214
            tags: '.h5p-placeholder',
215
            tagMatchRequiresAll: false
216
        });
217
 
218
        this.editor.all('.h5p-placeholder').setAttribute('contenteditable', 'false');
219
        this.editor.delegate('dblclick', this._handleDblClick, '.h5p-placeholder', this);
220
        this.editor.delegate('click', this._handleClick, '.h5p-placeholder', this);
221
    },
222
 
223
    /**
224
     * Handle a double click on a H5P Placeholder.
225
     *
226
     * @method _handleDblClick
227
     * @private
228
     */
229
    _handleDblClick: function() {
230
        this._displayDialogue();
231
    },
232
 
233
    /**
234
     * Handle a click on a H5P Placeholder.
235
     *
236
     * @method _handleClick
237
     * @param {EventFacade} e
238
     * @private
239
     */
240
    _handleClick: function(e) {
241
        var selection = this.get('host').getSelectionFromNode(e.target);
242
        if (this.get('host').getSelection() !== selection) {
243
            this.get('host').setSelection(selection);
244
        }
245
    },
246
 
247
    /**
248
     * Display the h5p editing tool.
249
     *
250
     * @method _displayDialogue
251
     * @private
252
     */
253
    _displayDialogue: function() {
254
        // Store the current selection.
255
        this._currentSelection = this.get('host').getSelection();
256
 
257
        if (this._currentSelection === false) {
258
            return;
259
        }
260
 
261
        this._getH5PDiv();
262
 
263
        var dialogue = this.getDialogue({
264
            headerContent: M.util.get_string('pluginname', COMPONENTNAME),
265
            width: 'auto',
266
            focusAfterHide: true
267
        });
268
        // Set the dialogue content, and then show the dialogue.
269
        dialogue.set('bodyContent', this._getDialogueContent())
270
            .show();
271
        M.form.shortforms({formid: this.get('host').get('elementid') + '_atto_h5p_form'});
272
    },
273
 
274
    /**
275
     * Get the H5P iframe
276
     *
277
     * @method _resolveH5P
278
     * @return {Node} The H5P iframe selected.
279
     * @private
280
     */
281
    _getH5PDiv: function() {
282
        var selectednodes = this.get('host').getSelectedNodes();
283
        var H5PDiv = null;
284
        selectednodes.each(function(selNode) {
285
            if (selNode.hasClass('h5p-placeholder')) {
286
                H5PDiv = selNode;
287
            }
288
        });
289
        this._H5PDiv = H5PDiv;
290
    },
291
 
292
    /**
293
     * Get the H5P button permissions.
294
     *
295
     * @return {Object} H5P button permissions.
296
     * @private
297
     */
298
    _getPermissions: function() {
299
        var permissions = {
300
            'canEmbed': false,
301
            'canUpload': false,
302
            'canUploadAndEmbed': false
303
        };
304
 
305
        if (this.get('host').canShowFilepicker('h5p')) {
306
            if (this._allowedmethods === 'both') {
307
                permissions.canUploadAndEmbed = true;
308
                permissions.canUpload = true;
309
            } else if (this._allowedmethods === 'upload') {
310
                permissions.canUpload = true;
311
            }
312
        }
313
 
314
        if (this._allowedmethods === 'both' || this._allowedmethods === 'embed') {
315
            permissions.canEmbed = true;
316
        }
317
        return permissions;
318
    },
319
 
320
 
321
    /**
322
     * Return the dialogue content for the tool, attaching any required
323
     * events.
324
     *
325
     * @method _getDialogueContent
326
     * @return {Node} The content to place in the dialogue.
327
     * @private
328
     */
329
    _getDialogueContent: function() {
330
 
331
        var permissions = this._getPermissions();
332
 
333
        var fileURL,
334
            optionDownloadButton,
335
            optionEmbedButton,
336
            optionCopyrightButton,
337
            showOptions = false;
338
 
339
        if (this._H5PDiv) {
340
            var H5PURL = this._H5PDiv.get('innerHTML');
341
            var fileBaseUrl = M.cfg.wwwroot + '/draftfile.php';
342
            if (fileBaseUrl == H5PURL.substring(0, fileBaseUrl.length)) {
343
                fileURL = H5PURL.split("?")[0];
344
 
345
                var parameters = H5PURL.split("?")[1];
346
                if (parameters) {
347
                    if (parameters.match(/export=1/)) {
348
                        optionDownloadButton = 'checked';
349
                        showOptions = true;
350
                    }
351
 
352
                    if (parameters.match(/embed=1/)) {
353
                        optionEmbedButton = 'checked';
354
                        showOptions = true;
355
                    }
356
 
357
                    if (parameters.match(/copyright=1/)) {
358
                        optionCopyrightButton = 'checked';
359
                        showOptions = true;
360
                    }
361
                }
362
            } else {
363
                fileURL = H5PURL;
364
            }
365
        }
366
 
367
        var template = Y.Handlebars.compile(TEMPLATE),
368
            content = Y.Node.create(template({
369
                elementid: this.get('host').get('elementid'),
370
                CSS: CSS,
371
                component: COMPONENTNAME,
372
                canUpload: permissions.canUpload,
373
                canEmbed: permissions.canEmbed,
374
                canUploadAndEmbed: permissions.canUploadAndEmbed,
375
                showOptions: showOptions,
376
                fileURL: fileURL,
377
                optionDownloadButton: optionDownloadButton,
378
                optionEmbedButton: optionEmbedButton,
379
                optionCopyrightButton: optionCopyrightButton
380
            }));
381
 
382
        this._form = content;
383
 
384
        // Listen to and act on Dialogue content events.
385
        this._setEventListeners();
386
 
387
        return content;
388
    },
389
 
390
    /**
391
     * Update the dialogue after an h5p was selected in the File Picker.
392
     *
393
     * @method _filepickerCallback
394
     * @param {object} params The parameters provided by the filepicker
395
     * containing information about the h5p.
396
     * @private
397
     */
398
    _filepickerCallback: function(params) {
399
        if (params.url !== '') {
400
            var input = this._form.one(SELECTORS.INPUTH5PFILE);
401
            input.set('value', params.url);
402
            this._removeWarnings();
403
        }
404
    },
405
 
406
    /**
407
     * Set event Listeners for Dialogue content actions.
408
     *
409
     * @method  _setEventListeners
410
     * @private
411
     */
412
    _setEventListeners: function() {
413
        var form = this._form;
414
        var permissions = this._getPermissions();
415
 
416
        form.one(SELECTORS.INPUTSUBMIT).on('click', this._setH5P, this);
417
 
418
        if (permissions.canUpload) {
419
            form.one(SELECTORS.H5PBROWSER).on('click', function() {
420
                this.get('host').showFilepicker('h5p', this._filepickerCallback, this);
421
            }, this);
422
        }
423
 
424
        if (permissions.canUploadAndEmbed) {
425
            form.one(SELECTORS.INPUTH5PFILE).on('change', function() {
426
                this._removeWarnings();
427
            }, this);
428
        }
429
    },
430
 
431
    /**
432
     * Remove warnings shown in the dialogue.
433
     *
434
     * @method _removeWarnings
435
     * @private
436
     */
437
    _removeWarnings: function() {
438
        var form = this._form;
439
        form.one(SELECTORS.URLWARNING).setStyle('display', 'none');
440
        form.one(SELECTORS.CONTENTWARNING).setStyle('display', 'none');
441
    },
442
 
443
    /**
444
     * Update the h5p in the contenteditable.
445
     *
446
     * @method _setH5P
447
     * @param {EventFacade} e
448
     * @private
449
     */
450
    _setH5P: function(e) {
451
        var form = this._form,
452
            h5phtml,
453
            host = this.get('host'),
454
            h5pfile = form.one(SELECTORS.INPUTH5PFILE).get('value'),
455
            permissions = this._getPermissions();
456
 
457
        e.preventDefault();
458
 
459
        // Check if there are any issues.
460
        if (this._updateWarning()) {
461
            return;
462
        }
463
 
464
        // Focus on the editor in preparation for inserting the H5P.
465
        host.focus();
466
 
467
        // Add an empty paragraph after new H5P container that can catch the cursor.
468
        var addParagraphs = true;
469
 
470
        // If a H5P placeholder was selected we can destroy it now.
471
        if (this._H5PDiv) {
472
            this._H5PDiv.remove();
473
            addParagraphs = false;
474
        }
475
 
476
        if (h5pfile !== '') {
477
            host.setSelection(this._currentSelection);
478
 
479
            if (h5pfile.startsWith(M.cfg.wwwroot)) {
480
                // It's a local file.
481
                var params = '';
482
                if (permissions.canUpload) {
483
                    var options = {};
484
                    if (form.one(SELECTORS.OPTION_DOWNLOAD_BUTTON).get('checked')) {
485
                        options['export'] = '1';
486
                    }
487
                    if (form.one(SELECTORS.OPTION_EMBED_BUTTON).get('checked')) {
488
                        options.embed = '1';
489
                    }
490
                    if (form.one(SELECTORS.OPTION_COPYRIGHT_BUTTON).get('checked')) {
491
                        options.copyright = '1';
492
                    }
493
 
494
                    for (var opt in options) {
495
                        if (params === "" && (h5pfile.indexOf("?") === -1)) {
496
                            params += "?";
497
                        } else {
498
                            params += "&amp;";
499
                        }
500
                        params += opt + "=" + options[opt];
501
                    }
502
                }
503
 
504
                var h5ptemplate = Y.Handlebars.compile(H5PTEMPLATE);
505
 
506
                h5phtml = h5ptemplate({
507
                    url: h5pfile + params,
508
                    addParagraphs: addParagraphs
509
                });
510
            } else {
511
                // It's a URL.
512
                var urltemplate = Y.Handlebars.compile(H5PTEMPLATE);
513
                h5phtml = urltemplate({
514
                    url: h5pfile
515
                });
516
            }
517
 
518
            host.insertContentAtFocusPoint(h5phtml);
519
 
520
            this.markUpdated();
521
        }
522
 
523
        this.getDialogue({
524
            focusAfterHide: null
525
        }).hide();
526
    },
527
 
528
    /**
529
     * Check if this could be a h5p embed.
530
     *
531
     * @method _validEmbed
532
     * @param {String} str
533
     * @return {boolean} whether this is a iframe tag.
534
     * @private
535
     */
536
    _validEmbed: function(str) {
537
        var pattern = new RegExp('^(<iframe).*(<\\/iframe>)'); // Port and path.
538
        return !!pattern.test(str);
539
    },
540
 
541
    /**
542
     * Check if this could be a h5p URL.
543
     *
544
     * @method _validURL
545
     * @param {String} str
546
     * @return {boolean} whether this is a valid URL.
547
     * @private
548
     */
549
    _validURL: function(str) {
550
        var pattern = new RegExp('^(https?:\\/\\/)?' + // Protocol.
551
            '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // Domain name.
552
            '((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address.
553
            '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'); // Port and path.
554
        return !!pattern.test(str);
555
    },
556
 
557
    /**
558
     * Update the url warning.
559
     *
560
     * @method _updateWarning
561
     * @return {boolean} whether a warning should be displayed.
562
     * @private
563
     */
564
    _updateWarning: function() {
565
        var form = this._form,
566
            state = true,
567
            h5pfile,
568
            permissions = this._getPermissions();
569
 
570
        if (permissions.canUpload || permissions.canEmbed) {
571
            h5pfile = form.one(SELECTORS.INPUTH5PFILE).get('value');
572
            if (h5pfile !== '') {
573
                form.one(SELECTORS.CONTENTWARNING).setStyle('display', 'none');
574
                if (h5pfile.startsWith(M.cfg.wwwroot) || this._validURL(h5pfile)) {
575
                    // Only external URLs have to be validated.
576
                    form.one(SELECTORS.URLWARNING).setStyle('display', 'none');
577
                    state = false;
578
                } else {
579
                    form.one(SELECTORS.URLWARNING).setStyle('display', 'block');
580
                    state = true;
581
                }
582
            } else {
583
                form.one(SELECTORS.CONTENTWARNING).setStyle('display', 'block');
584
                state = true;
585
            }
586
        }
587
 
588
        return state;
589
    }
590
}, {
591
    ATTRS: {
592
        /**
593
         * The allowedmethods of adding h5p content.
594
         *
595
         * @attribute allowedmethods
596
         * @type String
597
         */
598
        allowedmethods: {
599
            value: null
600
        }
601
    }
602
});