Proyectos de Subversion Moodle

Rev

Rev 11 | | Comparar con el anterior | 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
 * Javascript library for enableing a drag and drop upload interface
18
 *
19
 * @package    moodlecore
20
 * @subpackage form
21
 * @copyright  2011 Davo Smith
22
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
 
25
M.form_dndupload = {}
26
 
27
M.form_dndupload.init = function(Y, options) {
28
    var dnduploadhelper = {
29
        // YUI object.
30
        Y: null,
31
        // URL for upload requests
32
        url: M.cfg.wwwroot + '/repository/repository_ajax.php?action=upload',
33
        // options may include: itemid, acceptedtypes, maxfiles, maxbytes, clientid, repositoryid, author, contextid
34
        options: {},
35
        // itemid used for repository upload
36
        itemid: null,
37
        // accepted filetypes accepted by this form passed to repository
38
        acceptedtypes: [],
39
        // maximum size of files allowed in this form
40
        maxbytes: 0,
41
        // Maximum combined size of files allowed in this form. {@link FILE_AREA_MAX_BYTES_UNLIMITED}
42
        areamaxbytes: -1,
43
        // unqiue id of this form field used for html elements
44
        clientid: '',
45
        // upload repository id, used for upload
46
        repositoryid: 0,
47
        // container which holds the node which recieves drag events
48
        container: null,
49
        // filemanager element we are working with
50
        filemanager: null,
51
        // callback  to filepicker element to refesh when uploaded
52
        callback: null,
53
        // Nasty hack to distinguish between dragenter(first entry),
54
        // dragenter+dragleave(moving between child elements) and dragleave (leaving element)
55
        entercount: 0,
56
        pageentercount: 0,
57
        // Holds the progress bar elements for each file.
58
        progressbars: {},
59
        // Number of request in queue and number of request uploading.
60
        totalOfRequest: 0,
61
        // Number of request upload.
62
        numberOfRequestUpload: 0,
63
 
64
        /**
65
         * Initalise the drag and drop upload interface
66
         * Note: one and only one of options.filemanager and options.formcallback must be defined
67
         *
68
         * @param Y the YUI object
69
         * @param object options {
70
         *            itemid: itemid used for repository upload in this form
71
         *            acceptdtypes: accepted filetypes by this form
72
         *            maxfiles: maximum number of files this form allows
73
         *            maxbytes: maximum size of files allowed in this form
74
         *            areamaxbytes: maximum combined size of files allowed in this form
75
         *            clientid: unqiue id of this form field used for html elements
76
         *            contextid: id of the current cotnext
77
         *            containerid: htmlid of container
78
         *            repositories: array of repository objects passed from filepicker
79
         *            filemanager: filemanager element we are working with
80
         *            formcallback: callback  to filepicker element to refesh when uploaded
81
         *          }
82
         */
83
        init: function(Y, options) {
84
            this.Y = Y;
85
 
86
            if (!this.browser_supported()) {
87
                Y.one('body').addClass('dndnotsupported');
88
                return; // Browser does not support the required functionality
89
            }
90
 
91
            // try and retrieve enabled upload repository
92
            this.repositoryid = this.get_upload_repositoryid(options.repositories);
93
 
94
            if (!this.repositoryid) {
95
                Y.one('body').addClass('dndnotsupported');
96
                return; // no upload repository is enabled to upload to
97
            }
98
 
99
            Y.one('body').addClass('dndsupported');
100
 
101
            this.options = options;
102
            this.acceptedtypes = options.acceptedtypes;
103
            this.clientid = options.clientid;
104
            this.maxbytes = options.maxbytes;
105
            this.areamaxbytes = options.areamaxbytes;
106
            this.itemid = options.itemid;
107
            this.author = options.author;
108
            this.container = this.Y.one('#'+options.containerid);
109
 
110
            if (options.filemanager) {
111
                // Needed to tell the filemanager to redraw when files uploaded
112
                // and to check how many files are already uploaded
113
                this.filemanager = options.filemanager;
114
            } else if (options.formcallback) {
115
 
116
                // Needed to tell the filepicker to update when a new
117
                // file is uploaded
118
                this.callback = options.formcallback;
119
            } else {
120
                if (M.cfg.developerdebug) {
121
                    alert('dndupload: Need to define either options.filemanager or options.formcallback');
122
                }
123
                return;
124
            }
125
 
126
            this.init_events();
127
            this.init_page_events();
128
        },
129
 
130
        /**
131
         * Check the browser has the required functionality
132
         * @return true if browser supports drag/drop upload
133
         */
134
        browser_supported: function() {
135
 
136
            if (typeof FileReader == 'undefined') {
137
                return false;
138
            }
139
            if (typeof FormData == 'undefined') {
140
                return false;
141
            }
142
            return true;
143
        },
144
 
145
        /**
146
         * Get upload repoistory from array of enabled repositories
147
         *
148
         * @param array repositories repository objects passed from filepicker
149
         * @param returns int id of upload repository or false if not found
150
         */
151
        get_upload_repositoryid: function(repositories) {
152
            for (var i in repositories) {
153
                if (repositories[i].type == "upload") {
154
                    return repositories[i].id;
155
                }
156
            }
157
 
158
            return false;
159
        },
160
 
161
        /**
162
         * Initialise drag events on node container, all events need
163
         * to be processed for drag and drop to work
164
         */
165
        init_events: function() {
166
            this.Y.on('dragenter', this.drag_enter, this.container, this);
167
            this.Y.on('dragleave', this.drag_leave, this.container, this);
168
            this.Y.on('dragover',  this.drag_over,  this.container, this);
169
            this.Y.on('drop',      this.drop,      this.container, this);
170
        },
171
 
172
        /**
173
         * Initialise whole-page events (to show / hide the 'drop files here'
174
         * message)
175
         */
176
        init_page_events: function() {
177
            this.Y.on('dragenter', this.drag_enter_page, 'body', this);
178
            this.Y.on('dragleave', this.drag_leave_page, 'body', this);
1441 ariadna 179
            this.Y.on('dragover', function(e) {
180
                e.preventDefault();
181
            }, 'body', this);
1 efrain 182
            this.Y.on('drop', function() {
183
                this.pageentercount = 0;
184
                this.hide_drop_target();
1441 ariadna 185
            }, 'body', this);
1 efrain 186
        },
187
 
188
        /**
189
         * Check if the filemanager / filepicker is disabled
190
         * @return bool - true if disabled
191
         */
192
        is_disabled: function() {
193
            return (this.container.ancestor('.fitem.disabled') != null);
194
        },
195
 
196
        /**
197
         * Show the 'drop files here' message when file(s) are dragged
198
         * onto the page
199
         */
200
        drag_enter_page: function(e) {
201
            if (this.is_disabled()) {
202
                return false;
203
            }
204
            if (!this.has_files(e)) {
205
                return false;
206
            }
207
 
208
            this.pageentercount++;
209
            if (this.pageentercount >= 2) {
210
                this.pageentercount = 2;
211
                return false;
212
            }
213
 
214
            this.show_drop_target();
215
 
216
            return false;
217
        },
218
 
219
        /**
220
         * Hide the 'drop files here' message when file(s) are dragged off
221
         * the page again
222
         */
223
        drag_leave_page: function(e) {
224
            this.pageentercount--;
225
            if (this.pageentercount == 1) {
226
                return false;
227
            }
228
            this.pageentercount = 0;
229
 
230
            this.hide_drop_target();
231
 
232
            return false;
233
        },
234
 
235
        /**
236
         * Check if the drag contents are valid and then call
237
         * preventdefault / stoppropagation to let the browser know
238
         * we will handle this drag/drop
239
         *
240
         * @param e event object
241
         * @return boolean true if a valid file drag event
242
         */
243
        check_drag: function(e) {
244
            if (this.is_disabled()) {
245
                return false;
246
            }
247
            if (!this.has_files(e)) {
248
                return false;
249
            }
250
 
251
            e.preventDefault();
252
            e.stopPropagation();
253
 
254
            return true;
255
        },
256
 
257
        /**
258
         * Handle a dragenter event, highlight the destination node
259
         * when a suitable drag event occurs
260
         */
261
        drag_enter: function(e) {
262
            if (!this.check_drag(e)) {
263
                return true;
264
            }
265
 
266
            this.entercount++;
267
            if (this.entercount >= 2) {
268
                this.entercount = 2; // Just moved over a child element - nothing to do
269
                return false;
270
            }
271
 
272
            // These lines are needed if the user has dragged something directly
273
            // from application onto the 'fileupload' box, without crossing another
274
            // part of the page first
275
            this.pageentercount = 2;
276
            this.show_drop_target();
277
 
278
            this.show_upload_ready();
279
            return false;
280
        },
281
 
282
        /**
283
         * Handle a dragleave event, Remove the highlight if dragged from
284
         * node
285
         */
286
        drag_leave: function(e) {
287
            if (!this.check_drag(e)) {
288
                return true;
289
            }
290
 
291
            this.entercount--;
292
            if (this.entercount == 1) {
293
                return false; // Just moved over a child element - nothing to do
294
            }
295
 
296
            this.entercount = 0;
297
            this.hide_upload_ready();
298
            return false;
299
        },
300
 
301
        /**
302
         * Handle a dragover event. Required to intercept to prevent the browser from
303
         * handling the drag and drop event as normal
304
         */
305
        drag_over: function(e) {
306
            if (!this.check_drag(e)) {
307
                return true;
308
            }
309
 
310
            return false;
311
        },
312
 
313
        /**
314
         * Handle a drop event.  Remove the highlight and then upload each
315
         * of the files (until we reach the file limit, or run out of files)
316
         */
317
        drop: function(e) {
318
            if (!this.check_drag(e, true)) {
319
                return true;
320
            }
321
 
322
            this.entercount = 0;
323
            this.pageentercount = 0;
324
            this.hide_upload_ready();
325
            this.hide_drop_target();
326
 
327
            var files = e._event.dataTransfer.files;
328
            if (this.filemanager) {
329
                var options = {
330
                    files: files,
331
                    options: this.options,
332
                    repositoryid: this.repositoryid,
333
                    currentfilecount: this.filemanager.filecount, // All files uploaded.
334
                    currentfiles: this.filemanager.options.list, // Only the current folder.
335
                    callback: Y.bind('update_filemanager', this),
336
                    callbackprogress: Y.bind('update_progress', this),
337
                    callbackcancel: Y.bind('hide_progress', this),
338
                    callbackNumberOfRequestUpload: {
339
                        get: Y.bind('getNumberOfRequestUpload', this),
340
                        increase: Y.bind('increaseNumberOfRequestUpload', this),
341
                        decrease: Y.bind('decreaseNumberOfRequestUpload', this),
342
                        getTotal: Y.bind('getTotalRequestUpload', this),
343
                        increaseTotal: Y.bind('increaseTotalRequest', this),
344
                        reset: Y.bind('resetNumberOfRequestUpload', this)
345
                    },
346
                    callbackClearProgress: Y.bind('clear_progress', this),
347
                    callbackStartProgress: Y.bind('startProgress', this),
348
                };
349
                this.show_progress();
350
                var uploader = new dnduploader(options);
351
                uploader.start_upload();
352
            } else {
353
                if (files.length >= 1) {
354
                    options = {
355
                        files:[files[0]],
356
                        options: this.options,
357
                        repositoryid: this.repositoryid,
358
                        currentfilecount: 0,
359
                        currentfiles: [],
360
                        callback: Y.bind('update_filemanager', this),
361
                        callbackprogress: Y.bind('update_progress', this),
362
                        callbackcancel: Y.bind('hide_progress', this),
363
                        callbackNumberOfRequestUpload: {
364
                            get: Y.bind('getNumberOfRequestUpload', this),
365
                            increase: Y.bind('increaseNumberOfRequestUpload', this),
366
                            decrease: Y.bind('decreaseNumberOfRequestUpload', this),
367
                            getTotal: Y.bind('getTotalRequestUpload', this),
368
                            increaseTotal: Y.bind('increaseTotalRequest', this),
369
                            reset: Y.bind('resetNumberOfRequestUpload', this)
370
                        },
371
                        callbackClearProgress: Y.bind('clear_progress', this),
372
                        callbackStartProgress: Y.bind('startProgress', this),
373
                    };
374
                    this.show_progress();
375
                    uploader = new dnduploader(options);
376
                    uploader.start_upload();
377
                }
378
            }
379
 
380
            return false;
381
        },
382
 
383
        /**
384
         * Increase number of request upload.
385
         */
386
        increaseNumberOfRequestUpload: function() {
387
            this.numberOfRequestUpload++;
388
        },
389
 
390
        /**
391
         * Increase total request.
392
         *
393
         * @param {number} newFileCount Number of new files.
394
         */
395
        increaseTotalRequest: function(newFileCount) {
396
            this.totalOfRequest += newFileCount;
397
        },
398
 
399
        /**
400
         * Decrease number of request upload.
401
         */
402
        decreaseNumberOfRequestUpload: function() {
403
            this.numberOfRequestUpload--;
404
        },
405
 
406
        /**
407
         * Return number of request upload.
408
         *
409
         * @returns {number}
410
         */
411
        getNumberOfRequestUpload: function() {
412
            return this.numberOfRequestUpload;
413
        },
414
 
415
        /**
416
         * Return number of request upload.
417
         *
418
         * @returns {number}
419
         */
420
        getTotalRequestUpload: function() {
421
            return this.totalOfRequest;
422
        },
423
 
424
        /**
425
         * Return number of request upload.
426
         *
427
         * @returns {number}
428
         */
429
        resetNumberOfRequestUpload: function() {
430
            this.numberOfRequestUpload = 0;
431
            this.totalOfRequest = 0;
432
        },
433
 
434
        /**
435
         * Check to see if the drag event has any files in it
436
         *
437
         * @param e event object
438
         * @return boolean true if event has files
439
         */
440
        has_files: function(e) {
441
            // In some browsers, dataTransfer.types may be null for a
442
            // 'dragover' event, so ensure a valid Array is always
443
            // inspected.
444
            var types = e._event.dataTransfer.types || [];
445
            for (var i=0; i<types.length; i++) {
446
                if (types[i] == 'Files') {
447
                    return true;
448
                }
449
            }
450
            return false;
451
        },
452
 
453
        /**
454
         * Highlight the area where files could be dropped
455
         */
456
        show_drop_target: function() {
457
            this.container.addClass('dndupload-ready');
458
        },
459
 
460
        hide_drop_target: function() {
461
            this.container.removeClass('dndupload-ready');
462
        },
463
 
464
        /**
465
         * Highlight the destination node (ready to drop)
466
         */
467
        show_upload_ready: function() {
468
            this.container.addClass('dndupload-over');
469
        },
470
 
471
        /**
472
         * Remove highlight on destination node
473
         */
474
        hide_upload_ready: function() {
475
            this.container.removeClass('dndupload-over');
476
        },
477
 
478
        /**
479
         * Show the element showing the upload in progress
480
         */
481
        show_progress: function() {
482
            this.container.addClass('dndupload-inprogress');
483
        },
484
 
485
        /**
486
         * Hide the element showing upload in progress
487
         */
488
        hide_progress: function() {
489
            if (!Object.keys(this.progressbars).length) {
490
                this.container.removeClass('dndupload-inprogress');
491
            }
492
        },
493
 
494
        /**
495
         * Tell the attached filemanager element (if any) to refresh on file
496
         * upload
497
         */
498
        update_filemanager: function(params) {
499
            this.clear_progress();
500
            this.hide_progress();
501
            if (this.filemanager) {
502
                // update the filemanager that we've uploaded the files
503
                this.filemanager.filepicker_callback();
504
            } else if (this.callback) {
505
                this.callback(params);
506
            }
507
        },
508
 
509
        /**
510
         * Clear the all progress bars.
511
         */
512
        clear_progress: function() {
513
            var filename;
514
            for (filename in this.progressbars) {
515
                if (this.progressbars.hasOwnProperty(filename)) {
516
                    this.progressbars[filename].progressouter.remove(true);
517
                    delete this.progressbars[filename];
518
                }
519
            }
520
        },
521
 
522
        /**
523
         * Show the current progress of the uploaded file.
524
         */
525
        update_progress: function(filename, percent) {
526
            this.startProgress(filename);
527
            this.progressbars[filename].progressinner.setStyle('width', percent + '%');
528
            this.progressbars[filename].progressinner.setAttribute('aria-valuenow', percent);
529
            this.progressbars[filename].progressinnertext.setContent(percent + '% ' + M.util.get_string('complete', 'moodle'));
530
        },
531
 
532
        /**
533
         * Start to show the progress of the uploaded file.
534
         *
535
         * @param {String} filename Name of file upload.
536
         */
537
        startProgress: function(filename) {
538
            if (this.progressbars[filename] === undefined) {
539
                var dispfilename = filename;
540
                if (dispfilename.length > 50) {
541
                    dispfilename = dispfilename.substr(0, 49) + '&hellip;';
542
                }
543
                var progressouter = this.container.create('<div>' + dispfilename +
544
                    '<div class="progress">' +
545
                    '   <div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">' +
1441 ariadna 546
                    '       <span class="visually-hidden"></span>' +
1 efrain 547
                    '   </div>' +
548
                    '</div></div>');
549
                var progressinner = progressouter.one('.progress-bar');
1441 ariadna 550
                var progressinnertext = progressinner.one('.visually-hidden');
1 efrain 551
                var progresscontainer = this.container.one('.dndupload-progressbars');
552
                progresscontainer.appendChild(progressouter);
553
 
554
                this.progressbars[filename] = {
555
                    progressouter: progressouter,
556
                    progressinner: progressinner,
557
                    progressinnertext: progressinnertext
558
                };
559
            }
560
        }
561
    };
562
 
563
    var dnduploader = function(options) {
564
        dnduploader.superclass.constructor.apply(this, arguments);
565
    };
566
 
567
    Y.extend(dnduploader, Y.Base, {
568
        // The URL to send the upload data to.
569
        api: M.cfg.wwwroot+'/repository/repository_ajax.php',
570
        // Options passed into the filemanager/filepicker element.
571
        options: {},
572
        // The function to call when all uploads complete.
573
        callback: null,
574
        // The function to call as the upload progresses
575
        callbackprogress: null,
576
        // The function to call if the upload is cancelled
577
        callbackcancel: null,
578
        // The list of files dropped onto the element.
579
        files: null,
580
        // The ID of the 'upload' repository.
581
        repositoryid: 0,
582
        // Array of files already in the current folder (to check for name clashes).
583
        currentfiles: null,
584
        // Total number of files already uploaded (to check for exceeding limits).
585
        currentfilecount: 0,
586
        // Number of new files will be upload in this dndupload (to check for exceeding limits).
587
        newFileCount: 0,
588
        // Total size of the files present in the area.
589
        currentareasize: 0,
590
        // The list of files to upload.
591
        uploadqueue: [],
592
        // This list of files with name clashes.
593
        renamequeue: [],
594
        // Size of the current queue.
595
        queuesize: 0,
596
        // Set to true if the user has clicked on 'overwrite all'.
597
        overwriteall: false,
598
        // Set to true if the user has clicked on 'rename all'.
599
        renameall: false,
600
        // The file manager helper.
601
        filemanagerhelper: null,
602
        // The function to call as the number of request upload.
603
        callbackNumberOfRequestUpload: null,
604
        // The function to call as the clear progresses.
605
        callbackClearProgress: null,
606
        // The function to call as the start progress.
607
        callbackStartProgress: null,
608
 
609
        /**
610
         * Initialise the settings for the dnduploader
611
         * @param object params - includes:
612
         *                     options (copied from the filepicker / filemanager)
613
         *                     repositoryid - ID of the upload repository
614
         *                     callback - the function to call when uploads are complete
615
         *                     currentfiles - the list of files already in the current folder in the filemanager
616
         *                     currentfilecount - the total files already in the filemanager
617
         *                     files - the list of files to upload
618
         * @return void
619
         */
620
        initializer: function(params) {
621
            this.options = params.options;
622
            this.repositoryid = params.repositoryid;
623
            this.callback = params.callback;
624
            this.callbackprogress = params.callbackprogress;
625
            this.callbackcancel = params.callbackcancel;
626
            this.currentfiles = params.currentfiles;
627
            this.currentfilecount = params.currentfilecount;
628
            this.currentareasize = 0;
629
            this.filemanagerhelper = this.options.filemanager;
630
            this.callbackNumberOfRequestUpload = params.callbackNumberOfRequestUpload;
631
            this.callbackClearProgress = params.callbackClearProgress;
632
            this.callbackStartProgress = params.callbackStartProgress;
633
 
634
            // Retrieve the current size of the area.
635
            for (var i = 0; i < this.currentfiles.length; i++) {
636
                this.currentareasize += this.currentfiles[i].size;
637
            };
638
 
639
            if (!this.initialise_queue(params.files)) {
640
                if (this.callbackcancel) {
641
                    this.callbackcancel();
642
                }
643
            }
644
        },
645
 
646
        /**
647
         * Entry point for starting the upload process (starts by processing any
648
         * renames needed)
649
         */
650
        start_upload: function() {
651
            this.process_renames(); // Automatically calls 'do_upload' once renames complete.
652
        },
653
 
654
        /**
655
         * Display a message in a popup
656
         * @param string msg - the message to display
657
         * @param string type - 'error' or 'info'
658
         */
659
        print_msg: function(msg, type) {
660
            var header = M.util.get_string('error', 'moodle');
661
            if (type != 'error') {
662
                type = 'info'; // one of only two types excepted
663
                header = M.util.get_string('info', 'moodle');
664
            }
665
            if (!this.msg_dlg) {
666
                this.msg_dlg_node = Y.Node.create(M.core_filepicker.templates.message);
667
                this.msg_dlg_node.generateID();
668
 
669
                this.msg_dlg = new M.core.dialogue({
670
                    bodyContent: this.msg_dlg_node,
671
                    centered: true,
672
                    modal: true,
673
                    visible: false
674
                });
675
                this.msg_dlg.plug(Y.Plugin.Drag,{handles:['#'+this.msg_dlg_node.get('id')+' .yui3-widget-hd']});
676
                this.msg_dlg_node.one('.fp-msg-butok').on('click', function(e) {
677
                    e.preventDefault();
678
                    this.msg_dlg.hide();
679
                }, this);
680
            }
681
 
682
            this.msg_dlg.set('headerContent', header);
683
            this.msg_dlg_node.removeClass('fp-msg-info').removeClass('fp-msg-error').addClass('fp-msg-'+type)
684
            this.msg_dlg_node.one('.fp-msg-text').setContent(msg);
685
            this.msg_dlg.show();
686
        },
687
 
688
        /**
689
         * Check the size of each file and add to either the uploadqueue or, if there
690
         * is a name clash, the renamequeue
691
         * @param FileList files - the files to upload
692
         * @return void
693
         */
694
        initialise_queue: function(files) {
695
            this.uploadqueue = [];
696
            this.renamequeue = [];
697
            this.queuesize = 0;
698
 
699
            // Loop through the files and find any name clashes with existing files.
700
            var i;
701
            for (i=0; i<files.length; i++) {
702
                if (this.options.maxbytes > 0 && files[i].size > this.options.maxbytes) {
703
                    // Check filesize before attempting to upload.
704
                    var maxbytesdisplay = this.display_size(this.options.maxbytes);
705
                    this.print_msg(M.util.get_string('maxbytesfile', 'error', {
706
                            file: files[i].name,
707
                            size: maxbytesdisplay
708
                        }), 'error');
709
                    this.uploadqueue = []; // No uploads if one file is too big.
710
                    return;
711
                }
712
 
713
                if (this.has_name_clash(files[i].name)) {
714
                    this.renamequeue.push(files[i]);
715
                } else {
716
                    if (!this.add_to_upload_queue(files[i], files[i].name, false)) {
717
                        return false;
718
                    }
719
                }
720
                this.queuesize += files[i].size;
721
            }
722
            return true;
723
        },
724
 
725
        /**
726
         * Generate the display for file size
727
         * @param int size The size to convert to human readable form
728
         * @return string
729
         */
730
        display_size: function(size) {
731
            // This is snippet of code (with some changes) is from the display_size function in moodlelib.
732
            var gb = M.util.get_string('sizegb', 'moodle'),
733
                mb = M.util.get_string('sizemb', 'moodle'),
734
                kb = M.util.get_string('sizekb', 'moodle'),
735
                b  = M.util.get_string('sizeb', 'moodle');
736
 
737
            if (size >= 1073741824) {
738
                size = Math.round(size / 1073741824 * 10) / 10 + gb;
739
            } else if (size >= 1048576) {
740
                size = Math.round(size / 1048576 * 10) / 10 + mb;
741
            } else if (size >= 1024) {
742
                size = Math.round(size / 1024 * 10) / 10 + kb;
743
            } else {
744
                size = parseInt(size, 10) + ' ' + b;
745
            }
746
 
747
            return size;
748
        },
749
 
750
        /**
751
         * Add a single file to the uploadqueue, whilst checking the maxfiles limit
752
         * @param File file - the file to add
753
         * @param string filename - the name to give the file on upload
754
         * @param bool overwrite - true to overwrite the existing file
755
         * @return bool true if added successfully
756
         */
757
        add_to_upload_queue: function(file, filename, overwrite) {
758
            if (!overwrite) {
759
                this.newFileCount++;
760
            }
761
 
762
            // The value for "unlimited files" is -1, so 0 should mean 0.
763
            if (this.options.maxfiles >= 0 && this.getTotalNumberOfFiles() > this.options.maxfiles) {
764
                // Too many files - abort entire upload.
765
                this.uploadqueue = [];
766
                this.renamequeue = [];
767
                this.print_msg(M.util.get_string('maxfilesreached', 'moodle', this.options.maxfiles), 'error');
768
                return false;
769
            }
770
            // The new file will cause the area to reach its limit, we cancel the upload of all files.
771
            // -1 is the value defined by FILE_AREA_MAX_BYTES_UNLIMITED.
772
            if (this.options.areamaxbytes > -1) {
773
                var sizereached = this.currentareasize + this.queuesize + file.size;
774
                if (sizereached > this.options.areamaxbytes) {
775
                    this.uploadqueue = [];
776
                    this.renamequeue = [];
777
                    this.print_msg(M.util.get_string('maxareabytesreached', 'moodle'), 'error');
778
                    return false;
779
                }
780
            }
781
            this.uploadqueue.push({file:file, filename:filename, overwrite:overwrite});
782
            return true;
783
        },
784
 
785
        /**
786
         * Get total number of files: Number of uploaded files, number of files unloading in other dndupload,
787
         * number of files need to be upload in this dndupload.
788
         * @return number Total number of files.
789
         */
790
        getTotalNumberOfFiles: function() {
791
            // Get number of files we added into other dndupload.
792
            let totalOfFiles = 0;
793
            if(this.callbackNumberOfRequestUpload) {
794
                totalOfFiles = this.callbackNumberOfRequestUpload.getTotal();
795
            }
796
 
797
            return this.currentfilecount + this.newFileCount + totalOfFiles;
798
        },
799
 
800
        /**
801
         * Take the next file from the renamequeue and ask the user what to do with
802
         * it. Called recursively until the queue is empty, then calls do_upload.
803
         * @return void
804
         */
805
        process_renames: function() {
806
            if (this.renamequeue.length == 0) {
807
                // All rename processing complete - start the actual upload.
808
                if(this.callbackNumberOfRequestUpload && this.uploadqueue.length > 0) {
809
                    this.callbackNumberOfRequestUpload.increaseTotal(this.newFileCount);
810
                }
811
                this.do_upload();
812
                return;
813
            }
814
            var multiplefiles = (this.renamequeue.length > 1);
815
 
816
            // Get the next file from the rename queue.
817
            var file = this.renamequeue.shift();
818
            // Generate a non-conflicting name for it.
819
            var newname = this.generate_unique_name(file.name);
820
 
821
            // If the user has clicked on overwrite/rename ALL then process
822
            // this file, as appropriate, then process the rest of the queue.
823
            if (this.overwriteall) {
824
                if (this.add_to_upload_queue(file, file.name, true)) {
825
                    this.process_renames();
826
                }
827
                return;
828
            }
829
            if (this.renameall) {
830
                if (this.add_to_upload_queue(file, newname, false)) {
831
                    this.process_renames();
832
                }
833
                return;
834
            }
835
 
836
            // Ask the user what to do with this file.
837
            var self = this;
838
 
839
            var process_dlg_node;
840
            if (multiplefiles) {
841
                process_dlg_node = Y.Node.create(M.core_filepicker.templates.processexistingfilemultiple);
842
            } else {
843
                process_dlg_node = Y.Node.create(M.core_filepicker.templates.processexistingfile);
844
            }
845
            var node = process_dlg_node;
846
            node.generateID();
847
            var process_dlg = new M.core.dialogue({
848
                bodyContent: node,
849
                headerContent: M.util.get_string('fileexistsdialogheader', 'repository'),
850
                centered: true,
851
                modal: true,
852
                visible: false
853
            });
854
            process_dlg.plug(Y.Plugin.Drag,{handles:['#'+node.get('id')+' .yui3-widget-hd']});
855
 
856
            // Overwrite original.
857
            node.one('.fp-dlg-butoverwrite').on('click', function(e) {
858
                e.preventDefault();
859
                process_dlg.hide();
860
                if (self.add_to_upload_queue(file, file.name, true)) {
861
                    self.process_renames();
862
                }
863
            }, this);
864
 
865
            // Rename uploaded file.
866
            node.one('.fp-dlg-butrename').on('click', function(e) {
867
                e.preventDefault();
868
                process_dlg.hide();
869
                if (self.add_to_upload_queue(file, newname, false)) {
870
                    self.process_renames();
871
                }
872
            }, this);
873
 
874
            // Cancel all uploads.
875
            node.one('.fp-dlg-butcancel').on('click', function(e) {
876
                e.preventDefault();
877
                process_dlg.hide();
878
                if (self.callbackcancel) {
879
                    this.notifyUploadCompleted();
880
                    self.callbackClearProgress();
881
                    self.callbackcancel();
882
                }
883
            }, this);
884
 
885
            // When we are at the file limit, only allow 'overwrite', not rename.
886
            if (this.getTotalNumberOfFiles() == this.options.maxfiles) {
887
                node.one('.fp-dlg-butrename').setStyle('display', 'none');
888
                if (multiplefiles) {
889
                    node.one('.fp-dlg-butrenameall').setStyle('display', 'none');
890
                }
891
            }
892
 
893
            // If there are more files still to go, offer the 'overwrite/rename all' options.
894
            if (multiplefiles) {
895
                // Overwrite all original files.
896
                node.one('.fp-dlg-butoverwriteall').on('click', function(e) {
897
                    e.preventDefault();
898
                    process_dlg.hide();
899
                    this.overwriteall = true;
900
                    if (self.add_to_upload_queue(file, file.name, true)) {
901
                        self.process_renames();
902
                    }
903
                }, this);
904
 
905
                // Rename all new files.
906
                node.one('.fp-dlg-butrenameall').on('click', function(e) {
907
                    e.preventDefault();
908
                    process_dlg.hide();
909
                    this.renameall = true;
910
                    if (self.add_to_upload_queue(file, newname, false)) {
911
                        self.process_renames();
912
                    }
913
                }, this);
914
            }
915
            node.one('.fp-dlg-text').setContent(M.util.get_string('fileexists', 'moodle', file.name));
916
            process_dlg_node.one('.fp-dlg-butrename').setContent(M.util.get_string('renameto', 'repository', newname));
917
 
918
            // Destroy the dialog once it has been hidden.
919
            process_dlg.after('visibleChange', function(e) {
920
                if (!process_dlg.get('visible')) {
921
                    if (self.callbackcancel) {
922
                        self.callbackcancel();
923
                    }
924
                    process_dlg.destroy(true);
925
                }
926
            }, this);
927
 
928
            process_dlg.show();
929
        },
930
 
931
        /**
932
         * Trigger upload completed event.
933
         */
934
        notifyUploadCompleted: function() {
935
            require(['core_form/events'], function(FormEvent) {
936
                const elementId = this.filemanagerhelper ? this.filemanagerhelper.filemanager.get('id') : this.options.containerid;
937
                FormEvent.triggerUploadCompleted(elementId);
938
            }.bind(this));
939
         },
940
 
941
        /**
942
         * Trigger form upload start events.
943
         */
944
        notifyUploadStarted: function() {
945
            require(['core_form/events'], function(FormEvent) {
946
                const elementId = this.filemanagerhelper ? this.filemanagerhelper.filemanager.get('id') : this.options.containerid;
947
                FormEvent.triggerUploadStarted(elementId);
948
            }.bind(this));
949
        },
950
 
951
        /**
952
         * Checks if there is already a file with the given name in the current folder
953
         * or in the list of already uploading files
954
         * @param string filename - the name to test
955
         * @return bool true if the name already exists
956
         */
957
        has_name_clash: function(filename) {
958
            // Check against the already uploaded files
959
            var i;
960
            for (i=0; i<this.currentfiles.length; i++) {
961
                if (filename == this.currentfiles[i].filename) {
962
                    return true;
963
                }
964
            }
965
            // Check against the uploading files that have already been processed
966
            for (i=0; i<this.uploadqueue.length; i++) {
967
                if (filename == this.uploadqueue[i].filename) {
968
                    return true;
969
                }
970
            }
971
            return false;
972
        },
973
 
974
        /**
975
         * Gets a unique file name
976
         *
977
         * @param string filename
978
         * @return string the unique filename generated
979
         */
980
        generate_unique_name: function(filename) {
981
            // Loop through increating numbers until a unique name is found.
982
            while (this.has_name_clash(filename)) {
983
                filename = increment_filename(filename);
984
            }
985
            return filename;
986
        },
987
 
988
        /**
989
         * Upload the next file from the uploadqueue - called recursively after each
990
         * upload is complete, then handles the callback to the filemanager/filepicker
991
         * @param lastresult - the last result from the server
992
         */
993
        do_upload: function(lastresult) {
994
            if (this.uploadqueue.length > 0) {
995
                var filedetails = this.uploadqueue.shift();
996
                this.upload_file(filedetails.file, filedetails.filename, filedetails.overwrite);
997
            } else {
998
                if (this.callbackNumberOfRequestUpload && !this.callbackNumberOfRequestUpload.get()) {
999
                    this.uploadfinished(lastresult);
1000
                }
1001
            }
1002
        },
1003
 
1004
        /**
1005
         * Run the callback to the filemanager/filepicker
1006
         */
1007
        uploadfinished: function(lastresult) {
1008
            this.callbackNumberOfRequestUpload.reset();
1009
            this.callback(lastresult);
1010
        },
1011
 
1012
        /**
1013
         * Upload a single file via an AJAX call to the 'upload' repository. Automatically
1014
         * calls do_upload as each upload completes.
1015
         * @param File file - the file to upload
1016
         * @param string filename - the name to give the file
1017
         * @param bool overwrite - true if the existing file should be overwritten
1018
         */
1019
        upload_file: function(file, filename, overwrite) {
1020
 
1021
            // This would be an ideal place to use the Y.io function
1022
            // however, this does not support data encoded using the
1023
            // FormData object, which is needed to transfer data from
1024
            // the DataTransfer object into an XMLHTTPRequest
1025
            // This can be converted when the YUI issue has been integrated:
1026
            // http://yuilibrary.com/projects/yui3/ticket/2531274
1027
            var xhr = new XMLHttpRequest();
1028
            var self = this;
1029
            if (self.callbackNumberOfRequestUpload) {
1030
                self.callbackNumberOfRequestUpload.increase();
1031
            }
1032
 
1033
            // Start progress bar.
1034
            xhr.onloadstart = function() {
1035
                self.callbackStartProgress(filename);
1036
                self.notifyUploadStarted();
1037
            };
1038
 
1039
            // Update the progress bar
1040
            xhr.upload.addEventListener('progress', function(e) {
1041
                if (e.lengthComputable && self.callbackprogress) {
1042
                    var percentage = Math.round((e.loaded * 100) / e.total);
1043
                    self.callbackprogress(filename, percentage);
1044
                }
1045
            }, false);
1046
 
1047
            xhr.onreadystatechange = function() { // Process the server response
1048
                if (xhr.readyState == 4) {
1049
                    self.notifyUploadCompleted();
1050
                    if (xhr.status == 200) {
1051
                        var result = JSON.parse(xhr.responseText);
1052
                        if (result) {
1053
                            if (result.error) {
1054
                                self.print_msg(result.error, 'error'); // TODO add filename?
1055
                                self.uploadfinished();
1056
                            } else {
1057
                                // Only update the filepicker if there were no errors
1058
                                if (result.event == 'fileexists') {
1059
                                    // Do not worry about this, as we only care about the last
1060
                                    // file uploaded, with the filepicker
1061
                                    result.file = result.newfile.filename;
1062
                                    result.url = result.newfile.url;
1063
                                }
1064
                                result.client_id = self.options.clientid;
1065
                                if (self.callbackprogress) {
1066
                                    self.callbackprogress(filename, 100);
1067
                                }
1068
                            }
1069
                        }
1070
                        if (self.callbackNumberOfRequestUpload) {
1071
                            self.callbackNumberOfRequestUpload.decrease();
1072
                        }
1073
                        self.do_upload(result); // continue uploading
1074
                    } else {
1075
                        self.print_msg(M.util.get_string('serverconnection', 'error'), 'error');
1076
                        self.uploadfinished();
1077
                    }
1078
                }
1079
            };
1080
 
1081
            // Prepare the data to send
1082
            var formdata = new FormData();
1083
            formdata.append('repo_upload_file', file); // The FormData class allows us to attach a file
1084
            formdata.append('sesskey', M.cfg.sesskey);
1085
            formdata.append('repo_id', this.repositoryid);
1086
            formdata.append('itemid', this.options.itemid);
1087
            if (this.options.author) {
1088
                formdata.append('author', this.options.author);
1089
            }
11 efrain 1090
            if (this.options.filemanager && this.options.filemanager.currentpath) { // Filepickers do not have folders
1 efrain 1091
                formdata.append('savepath', this.options.filemanager.currentpath);
11 efrain 1092
            } else {
1093
                formdata.append('savepath', '/');
1 efrain 1094
            }
1095
            formdata.append('title', filename);
1096
            if (overwrite) {
1097
                formdata.append('overwrite', 1);
1098
            }
1099
            if (this.options.contextid) {
1100
                formdata.append('ctx_id', this.options.contextid);
1101
            }
1102
 
1103
            // Accepted types can be either a string or an array, but an array is
1104
            // expected in the processing script, so make sure we are sending an array
1105
            if (this.options.acceptedtypes.constructor == Array) {
1106
                for (var i=0; i<this.options.acceptedtypes.length; i++) {
1107
                    formdata.append('accepted_types[]', this.options.acceptedtypes[i]);
1108
                }
1109
            } else {
1110
                formdata.append('accepted_types[]', this.options.acceptedtypes);
1111
            }
1112
 
1113
            // Send the file & required details.
1114
            var uploadUrl = this.api;
1115
            if (uploadUrl.indexOf('?') !== -1) {
1116
                uploadUrl += '&action=upload';
1117
            } else {
1118
                uploadUrl += '?action=upload';
1119
            }
1120
            xhr.open("POST", uploadUrl, true);
1121
            xhr.send(formdata);
1122
            return true;
1123
        }
1124
    });
1125
 
1126
    dnduploadhelper.init(Y, options);
1127
};