Merge branch 'MDL-51434-master' of git://github.com/jleyva/moodle
[moodle.git] / lib / form / filemanager.js
blob6b019880cd2d3abd3af307127de9580d061f33f2
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.
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  * File Manager UI
18  * =====
19  * this.api, stores the URL to make ajax request
20  * this.currentpath
21  * this.filepicker_options
22  * this.movefile_dialog
23  * this.mkdir_dialog
24  * this.rename_dialog
25  * this.client_id
26  * this.filecount, how many files in this filemanager
27  * this.maxfiles
28  * this.maxbytes
29  * this.areamaxbytes, the maximum size of the area
30  * this.filemanager, contains reference to filemanager Node
31  * this.selectnode, contains referenct to select-file Node
32  * this.selectui, M.core.dialogue to select the file
33  *
34  * FileManager options:
35  * =====
36  * this.options.currentpath
37  * this.options.itemid
38  */
41 M.form_filemanager = {templates:{}};
43 M.form_filemanager.set_templates = function(Y, templates) {
44     M.form_filemanager.templates = templates;
47 /**
48  * This fucntion is called for each file picker on page.
49  */
50 M.form_filemanager.init = function(Y, options) {
51     var FileManagerHelper = function(options) {
52         FileManagerHelper.superclass.constructor.apply(this, arguments);
53     };
54     FileManagerHelper.NAME = "FileManager";
55     FileManagerHelper.ATTRS = {
56         options: {},
57         lang: {}
58     };
60     Y.extend(FileManagerHelper, Y.Base, {
61         api: M.cfg.wwwroot+'/repository/draftfiles_ajax.php',
62         menus: {},
63         initializer: function(options) {
64             this.options = options;
65             if (options.mainfile) {
66                 this.enablemainfile = options.mainfile;
67             }
68             this.client_id = options.client_id;
69             this.currentpath = '/';
70             this.maxfiles = options.maxfiles;
71             this.maxbytes = options.maxbytes;
72             this.areamaxbytes = options.areamaxbytes;
73             this.emptycallback = null; // Used by drag and drop upload
75             this.filepicker_options = options.filepicker?options.filepicker:{};
76             this.filepicker_options.client_id = this.client_id;
77             this.filepicker_options.context = options.context;
78             this.filepicker_options.maxfiles = this.maxfiles;
79             this.filepicker_options.maxbytes = this.maxbytes;
80             this.filepicker_options.areamaxbytes = this.areamaxbytes;
81             this.filepicker_options.env = 'filemanager';
82             this.filepicker_options.itemid = options.itemid;
84             if (options.filecount) {
85                 this.filecount = options.filecount;
86             } else {
87                 this.filecount = 0;
88             }
89             // prepare filemanager for drag-and-drop upload
90             this.filemanager = Y.one('#filemanager-'+options.client_id);
91             if (this.filemanager.hasClass('filemanager-container') || !this.filemanager.one('.filemanager-container')) {
92                 this.dndcontainer = this.filemanager;
93             } else  {
94                 this.dndcontainer = this.filemanager.one('.filemanager-container');
95                 if (!this.dndcontainer.get('id')) {
96                     this.dndcontainer.generateID();
97                 }
98             }
99             // save template for one path element and location of path bar
100             if (this.filemanager.one('.fp-path-folder')) {
101                 this.pathnode = this.filemanager.one('.fp-path-folder');
102                 this.pathbar = this.pathnode.get('parentNode');
103                 this.pathbar.removeChild(this.pathnode);
104             }
105             // initialize 'select file' panel
106             this.selectnode = Y.Node.createWithFilesSkin(M.form_filemanager.templates.fileselectlayout);
107             this.selectnode.setAttribute('aria-live', 'assertive');
108             this.selectnode.setAttribute('role', 'dialog');
109             this.selectnode.generateID();
111             var labelid = 'fm-dialog-label_'+ this.selectnode.get('id');
112             this.selectui = new M.core.dialogue({
113                 draggable    : true,
114                 headerContent: '<h3 id="' + labelid +'">' + M.util.get_string('edit', 'moodle') + '</h3>',
115                 bodyContent  : this.selectnode,
116                 centered     : true,
117                 width        : '480px',
118                 modal        : true,
119                 visible      : false
120             });
121             Y.one('#'+this.selectnode.get('id')).setAttribute('aria-labelledby', labelid);
122             this.selectui.hide();
123             this.setup_select_file();
124             // setup buttons onclick events
125             this.setup_buttons();
126             // set event handler for lazy loading of thumbnails
127             this.filemanager.one('.fp-content').on(['scroll','resize'], this.content_scrolled, this);
128             // display files
129             this.viewmode = 1; // TODO take from cookies?
130             this.filemanager.all('.fp-vb-icons,.fp-vb-tree,.fp-vb-details').removeClass('checked')
131             this.filemanager.all('.fp-vb-icons').addClass('checked')
132             this.refresh(this.currentpath); // MDL-31113 get latest list from server
133         },
135         wait: function() {
136            this.filemanager.addClass('fm-updating');
137         },
138         request: function(args, redraw) {
139             var api = this.api + '?action='+args.action;
140             var params = {};
141             var scope = this;
142             if (args['scope']) {
143                 scope = args['scope'];
144             }
145             params['sesskey'] = M.cfg.sesskey;
146             params['client_id'] = this.client_id;
147             params['filepath'] = this.currentpath;
148             params['itemid'] = this.options.itemid?this.options.itemid:0;
149             if (args['params']) {
150                 for (i in args['params']) {
151                     params[i] = args['params'][i];
152                 }
153             }
154             var cfg = {
155                 method: 'POST',
156                 on: {
157                     complete: function(id,o,p) {
158                         if (!o) {
159                             alert('IO FATAL');
160                             return;
161                         }
162                         var data = null;
163                         try {
164                             data = Y.JSON.parse(o.responseText);
165                         } catch(e) {
166                             scope.print_msg(M.util.get_string('invalidjson', 'repository'), 'error');
167                             Y.error(M.util.get_string('invalidjson', 'repository')+":\n"+o.responseText);
168                             return;
169                         }
170                         if (data && data.tree && scope.set_current_tree) {
171                             scope.set_current_tree(data.tree);
172                         }
173                         args.callback(id,data,p);
174                     }
175                 },
176                 arguments: {
177                     scope: scope
178                 },
179                 headers: {
180                     'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
181                 },
182                 data: build_querystring(params)
183             };
184             if (args.form) {
185                 cfg.form = args.form;
186             }
187             Y.io(api, cfg);
188             if (redraw) {
189                 this.wait();
190             }
191         },
192         filepicker_callback: function(obj) {
193             this.filecount++;
194             this.check_buttons();
195             this.refresh(this.currentpath);
196             if (typeof M.core_formchangechecker != 'undefined') {
197                 M.core_formchangechecker.set_form_changed();
198             }
199         },
200         check_buttons: function() {
201             if (this.filecount>0) {
202                 this.filemanager.removeClass('fm-nofiles');
203             } else {
204                 this.filemanager.addClass('fm-nofiles');
205             }
206             if (this.filecount >= this.maxfiles && this.maxfiles!=-1) {
207                 this.filemanager.addClass('fm-maxfiles');
208             }
209             else {
210                 this.filemanager.removeClass('fm-maxfiles');
211             }
212         },
213         refresh: function(filepath) {
214             var scope = this;
215             this.currentpath = filepath;
216             if (!filepath) {
217                 filepath = this.currentpath;
218             } else {
219                 this.currentpath = filepath;
220             }
221             this.request({
222                 action: 'list',
223                 scope: scope,
224                 params: {'filepath':filepath},
225                 callback: function(id, obj, args) {
226                     scope.filecount = obj.filecount;
227                     scope.options = obj;
228                     scope.lazyloading = {};
229                     scope.check_buttons();
230                     scope.render(obj);
231                 }
232             }, true);
233         },
234         /** displays message in a popup */
235         print_msg: function(msg, type) {
236             var header = M.util.get_string('error', 'moodle');
237             if (type != 'error') {
238                 type = 'info'; // one of only two types excepted
239                 header = M.util.get_string('info', 'moodle');
240             }
241             if (!this.msg_dlg) {
242                 this.msg_dlg_node = Y.Node.createWithFilesSkin(M.form_filemanager.templates.message);
243                 var nodeid = this.msg_dlg_node.generateID();
245                 this.msg_dlg = new M.core.dialogue({
246                     draggable    : true,
247                     bodyContent  : this.msg_dlg_node,
248                     centered     : true,
249                     modal        : true,
250                     visible      : false,
251                 });
252                 this.msg_dlg_node.one('.fp-msg-butok').on('click', function(e) {
253                     e.preventDefault();
254                     this.msg_dlg.hide();
255                 }, this);
256             }
258             this.msg_dlg.set('headerContent', header);
259             this.msg_dlg_node.removeClass('fp-msg-info').removeClass('fp-msg-error').addClass('fp-msg-'+type)
260             this.msg_dlg_node.one('.fp-msg-text').setContent(Y.Escape.html(msg));
261             this.msg_dlg.show();
262         },
263         is_disabled: function() {
264             return this.filemanager.ancestor('.fitem.disabled') != null;
265         },
266         setup_buttons: function() {
267             var button_download = this.filemanager.one('.fp-btn-download');
268             var button_create   = this.filemanager.one('.fp-btn-mkdir');
269             var button_addfile  = this.filemanager.one('.fp-btn-add');
271             // setup 'add file' button
272             button_addfile.on('click', this.show_filepicker, this);
274             var dndarrow = this.filemanager.one('.dndupload-arrow');
275             if (dndarrow) {
276                 dndarrow.on('click', this.show_filepicker, this);
277             }
279             // setup 'make a folder' button
280             if (this.options.subdirs) {
281                 button_create.on('click',function(e) {
282                     e.preventDefault();
283                     if (this.is_disabled()) {
284                         return;
285                     }
286                     var scope = this;
287                     // a function used to perform an ajax request
288                     var perform_action = function(e) {
289                         e.preventDefault();
290                         var foldername = Y.one('#fm-newname-'+scope.client_id).get('value');
291                         if (!foldername) {
292                             scope.mkdir_dialog.hide();
293                             return;
294                         }
295                         scope.request({
296                             action:'mkdir',
297                             params: {filepath:scope.currentpath, newdirname:foldername},
298                             callback: function(id, obj, args) {
299                                 var filepath = obj.filepath;
300                                 scope.mkdir_dialog.hide();
301                                 scope.refresh(filepath);
302                                 Y.one('#fm-newname-'+scope.client_id).set('value', '');
303                                 if (typeof M.core_formchangechecker != 'undefined') {
304                                     M.core_formchangechecker.set_form_changed();
305                                 }
306                             }
307                         });
308                     };
309                     var validate_folder_name = function() {
310                         var valid = false;
311                         var foldername = Y.one('#fm-newname-'+scope.client_id).get('value');
312                         if (foldername.length > 0) {
313                             valid = true;
314                         }
315                         var btn = Y.one('#fm-mkdir-butcreate-'+scope.client_id);
316                         if (btn) {
317                             btn.set('disabled', !valid);
318                         }
319                         return valid;
320                     };
321                     if (!this.mkdir_dialog) {
322                         var node = Y.Node.createWithFilesSkin(M.form_filemanager.templates.mkdir);
323                         this.mkdir_dialog = new M.core.dialogue({
324                             draggable    : true,
325                             bodyContent  : node,
326                             centered     : true,
327                             modal        : true,
328                             visible      : false,
329                         });
330                         node.one('.fp-dlg-butcreate').set('id', 'fm-mkdir-butcreate-'+this.client_id).on('click',
331                                 perform_action, this);
332                         node.one('input').set('id', 'fm-newname-'+this.client_id).on('keydown', function(e) {
333                             var valid = Y.bind(validate_folder_name, this)();
334                             if (valid && e.keyCode === 13) {
335                                 Y.bind(perform_action, this)(e);
336                             }
337                         }, this);
338                         node.one('#fm-newname-'+this.client_id).on(['keyup', 'change'], function(e) {
339                             Y.bind(validate_folder_name, this)();
340                         }, this);
342                         node.one('label').set('for', 'fm-newname-' + this.client_id);
343                         node.all('.fp-dlg-butcancel').on('click', function(e){e.preventDefault();this.mkdir_dialog.hide();}, this);
344                         node.all('.fp-dlg-curpath').set('id', 'fm-curpath-'+this.client_id);
345                     }
346                     this.mkdir_dialog.show();
348                     // Default folder name:
349                     var foldername = M.util.get_string('newfolder', 'repository');
350                     while (this.has_folder(foldername)) {
351                         foldername = increment_filename(foldername, true);
352                     }
353                     Y.one('#fm-newname-'+scope.client_id).set('value', foldername);
354                     Y.bind(validate_folder_name, this)();
355                     Y.one('#fm-newname-'+scope.client_id).focus().select();
356                     Y.all('#fm-curpath-'+scope.client_id).setContent(this.currentpath);
357                 }, this);
358             } else {
359                 this.filemanager.addClass('fm-nomkdir');
360             }
362             // setup 'download this folder' button
363             button_download.on('click',function(e) {
364                 e.preventDefault();
365                 if (this.is_disabled()) {
366                     return;
367                 }
368                 var scope = this;
370                 var image_downloading = this.filemanager.one('.fp-img-downloading');
371                 if (image_downloading.getStyle('display') == 'inline') {
372                     return;
373                 }
374                 image_downloading.setStyle('display', 'inline');
376                 // perform downloaddir ajax request
377                 this.request({
378                     action: 'downloaddir',
379                     scope: scope,
380                     callback: function(id, obj, args) {
381                         var image_downloading = scope.filemanager.one('.fp-img-downloading');
382                         image_downloading.setStyle('display', 'none');
384                         if (obj) {
385                             scope.refresh(obj.filepath);
386                             node = Y.Node.create('<iframe></iframe>').setStyles({
387                                 visibility : 'hidden',
388                                 width : '1px',
389                                 height : '1px'
390                             });
391                             node.set('src', obj.fileurl);
392                             Y.one('body').appendChild(node);
393                         } else {
394                             scope.print_msg(M.util.get_string('draftareanofiles', 'repository'), 'error');
395                         }
396                     }
397                 });
398             }, this);
400             this.filemanager.all('.fp-vb-icons,.fp-vb-tree,.fp-vb-details').
401                 on('click', function(e) {
402                     e.preventDefault();
403                     var viewbar = this.filemanager.one('.fp-viewbar')
404                     if (!this.is_disabled() && (!viewbar || !viewbar.hasClass('disabled'))) {
405                         this.filemanager.all('.fp-vb-icons,.fp-vb-tree,.fp-vb-details').removeClass('checked')
406                         if (e.currentTarget.hasClass('fp-vb-tree')) {
407                             this.viewmode = 2;
408                         } else if (e.currentTarget.hasClass('fp-vb-details')) {
409                             this.viewmode = 3;
410                         } else {
411                             this.viewmode = 1;
412                         }
413                         e.currentTarget.addClass('checked')
414                         this.render();
415                         this.filemanager.one('.fp-content').setAttribute('tabIndex', '0');
416                         this.filemanager.one('.fp-content').focus();
417                     }
418                 }, this);
419         },
421         show_filepicker: function (e) {
422             // if maxfiles == -1, the no limit
423             e.preventDefault();
424             if (this.is_disabled()) {
425                 return;
426             }
427             var options = this.filepicker_options;
428             options.formcallback = this.filepicker_callback;
429             // XXX: magic here, to let filepicker use filemanager scope
430             options.magicscope = this;
431             options.savepath = this.currentpath;
432             M.core_filepicker.show(Y, options);
433         },
435         print_path: function() {
436             var p = this.options.path;
437             this.pathbar.setContent('').addClass('empty');
438             if (p && p.length!=0 && this.viewmode != 2) {
439                 for(var i = 0; i < p.length; i++) {
440                     var el = this.pathnode.cloneNode(true);
441                     this.pathbar.appendChild(el);
443                     if (i == 0) {
444                         el.addClass('first');
445                     }
446                     if (i == p.length-1) {
447                         el.addClass('last');
448                     }
450                     if (i%2) {
451                         el.addClass('even');
452                     } else {
453                         el.addClass('odd');
454                     }
455                     el.one('.fp-path-folder-name').setContent(Y.Escape.html(p[i].name)).
456                         on('click', function(e, path) {
457                             e.preventDefault();
458                             if (!this.is_disabled()) {
459                                 this.refresh(path);
460                             }
461                         }, this, p[i].path);
462                 }
463                 this.pathbar.removeClass('empty');
464             }
465         },
466         get_filepath: function(obj) {
467             if (obj.path && obj.path.length) {
468                 return obj.path[obj.path.length-1].path;
469             }
470             return '';
471         },
472         treeview_dynload: function(node, cb) {
473             var retrieved_children = {};
474             if (node.children) {
475                 for (var i in node.children) {
476                     retrieved_children[node.children[i].path] = node.children[i];
477                 }
478             }
479             if (!node.path || node.path == '/') {
480                 // this is a root pseudo folder
481                 node.fileinfo.filepath = '/';
482                 node.fileinfo.type = 'folder';
483                 node.fileinfo.fullname = node.fileinfo.title;
484                 node.fileinfo.filename = '.';
485             }
486             this.request({
487                 action:'list',
488                 params: {filepath:node.path?node.path:''},
489                 scope:this,
490                 callback: function(id, obj, args) {
491                     var list = obj.list;
492                     var scope = args.scope;
493                     // check that user did not leave the view mode before recieving this response
494                     if (!(scope.viewmode == 2 && node && node.getChildrenEl())) {
495                         return;
496                     }
497                     if (cb != null) { // (in manual mode do not update current path)
498                         scope.options = obj;
499                         scope.currentpath = node.path?node.path:'/';
500                     }
501                     node.highlight(false);
502                     node.origlist = obj.list ? obj.list : null;
503                     node.origpath = obj.path ? obj.path : null;
504                     node.children = [];
505                     for(k in list) {
506                         if (list[k].type == 'folder' && retrieved_children[list[k].filepath]) {
507                             // if this child is a folder and has already been retrieved
508                             retrieved_children[list[k].filepath].fileinfo = list[k];
509                             node.children[node.children.length] = retrieved_children[list[k].filepath];
510                         } else {
511                             // append new file to the list
512                             scope.view_files([list[k]]);
513                         }
514                     }
515                     if (cb == null) {
516                         node.refresh();
517                     } else {
518                         // invoke callback requested by TreeView component
519                         cb();
520                     }
521                     scope.content_scrolled();
522                 }
523             }, false);
524         },
525         content_scrolled: function(e) {
526             setTimeout(Y.bind(function() {
527                 if (this.processingimages) {return;}
528                 this.processingimages = true;
529                 var scope = this,
530                     fpcontent = this.filemanager.one('.fp-content'),
531                     fpcontenty = fpcontent.getY(),
532                     fpcontentheight = fpcontent.getStylePx('height'),
533                     is_node_visible = function(node) {
534                         var offset = node.getY()-fpcontenty;
535                         if (offset <= fpcontentheight && (offset >=0 || offset+node.getStylePx('height')>=0)) {
536                             return true;
537                         }
538                         return false;
539                     };
540                 // replace src for visible images that need to be lazy-loaded
541                 if (scope.lazyloading) {
542                     fpcontent.all('img').each( function(node) {
543                         if (node.get('id') && scope.lazyloading[node.get('id')] && is_node_visible(node)) {
544                             node.setImgRealSrc(scope.lazyloading);
545                         }
546                     });
547                 }
548                 this.processingimages = false;
549             }, this), 200)
550         },
551         view_files: function(appendfiles) {
552             this.filemanager.removeClass('fm-updating').removeClass('fm-noitems');
553             if ((appendfiles == null) && (!this.options.list || this.options.list.length == 0) && this.viewmode != 2) {
554                 this.filemanager.addClass('fm-noitems');
555                 return;
556             }
557             var list = (appendfiles != null) ? appendfiles : this.options.list;
558             var element_template;
559             if (this.viewmode == 2 || this.viewmode == 3) {
560                 element_template = Y.Node.create(M.form_filemanager.templates.listfilename);
561             } else {
562                 this.viewmode = 1;
563                 element_template = Y.Node.create(M.form_filemanager.templates.iconfilename);
564             }
565             var options = {
566                 viewmode : this.viewmode,
567                 appendonly : appendfiles != null,
568                 filenode : element_template,
569                 callbackcontext : this,
570                 callback : function(e, node) {
571                     if (e.preventDefault) { e.preventDefault(); }
572                     if (node.type == 'folder') {
573                         this.refresh(node.filepath);
574                     } else {
575                         this.select_file(node);
576                     }
577                 },
578                 rightclickcallback : function(e, node) {
579                     if (e.preventDefault) { e.preventDefault(); }
580                     this.select_file(node);
581                 },
582                 classnamecallback : function(node) {
583                     var classname = '';
584                     if (node.type == 'folder' || (!node.type && !node.filename)) {
585                         classname = classname + ' fp-folder';
586                     }
587                     if (node.filename || node.filepath || (node.path && node.path != '/')) {
588                         classname = classname + ' fp-hascontextmenu';
589                     }
590                     if (node.isref) {
591                         classname = classname + ' fp-isreference';
592                     }
593                     if (node.refcount) {
594                         classname = classname + ' fp-hasreferences';
595                     }
596                     if (node.originalmissing) {
597                         classname = classname + ' fp-originalmissing';
598                     }
599                     if (node.sortorder == 1) { classname = classname + ' fp-mainfile';}
600                     return Y.Lang.trim(classname);
601                 }
602             };
603             if (this.viewmode == 2) {
604                 options.dynload = true;
605                 options.filepath = this.options.path;
606                 options.treeview_dynload = this.treeview_dynload;
607                 options.norootrightclick = true;
608                 options.callback = function(e, node) {
609                     // TODO MDL-32736 e is not an event here but an object with properties 'event' and 'node'
610                     if (!node.fullname) {return;}
611                     if (node.type != 'folder') {
612                         if (e.node.parent && e.node.parent.origpath) {
613                             // set the current path
614                             this.options.path = e.node.parent.origpath;
615                             this.options.list = e.node.parent.origlist;
616                             this.print_path();
617                         }
618                         this.currentpath = node.filepath;
619                         this.select_file(node);
620                     } else {
621                         // save current path and filelist (in case we want to jump to other viewmode)
622                         this.options.path = e.node.origpath;
623                         this.options.list = e.node.origlist;
624                         this.currentpath = node.filepath;
625                         this.print_path();
626                         //this.content_scrolled();
627                     }
628                 };
629             }
630             if (!this.lazyloading) {
631                 this.lazyloading={};
632             }
633             this.filemanager.one('.fp-content').fp_display_filelist(options, list, this.lazyloading);
634             this.content_scrolled();
635         },
636         populate_licenses_select: function(node) {
637             if (!node) {
638                 return;
639             }
640             node.setContent('');
641             var licenses = this.options.licenses;
642             for (var i in licenses) {
643                 var option = Y.Node.create('<option/>').
644                     set('value', licenses[i].shortname).
645                     setContent(Y.Escape.html(licenses[i].fullname));
646                 node.appendChild(option)
647             }
648         },
649         set_current_tree: function(tree) {
650             var appendfilepaths = function(list, node) {
651                 if (!node || !node.children || !node.children.length) {return;}
652                 for (var i in node.children) {
653                     list[list.length] = node.children[i].filepath;
654                     appendfilepaths(list, node.children[i]);
655                 }
656             }
657             var list = ['/'];
658             appendfilepaths(list, tree);
659             var selectnode = this.selectnode;
660             node = selectnode.one('.fp-path select');
661             node.setContent('');
662             for (var i in list) {
663                 node.appendChild(Y.Node.create('<option/>').
664                     set('value', list[i]).setContent(Y.Escape.html(list[i])));
665             }
666         },
667         update_file: function(confirmed) {
668             var selectnode = this.selectnode;
669             var fileinfo = this.selectui.fileinfo;
671             var newfilename = Y.Lang.trim(selectnode.one('.fp-saveas input').get('value'));
672             var filenamechanged = (newfilename && newfilename != fileinfo.fullname);
673             var pathselect = selectnode.one('.fp-path select'),
674                     pathindex = pathselect.get('selectedIndex'),
675                     targetpath = pathselect.get("options").item(pathindex).get('value');
676             var filepathchanged = (targetpath != this.get_parent_folder_name(fileinfo));
677             var newauthor = Y.Lang.trim(selectnode.one('.fp-author input').get('value'));
678             var authorchanged = (newauthor != Y.Lang.trim(fileinfo.author));
679             var licenseselect = selectnode.one('.fp-license select'),
680                     licenseindex = licenseselect.get('selectedIndex'),
681                     newlicense = licenseselect.get("options").item(licenseindex).get('value');
682             var licensechanged = (newlicense != fileinfo.license);
684             var params, action;
685             var dialog_options = {callback:this.update_file, callbackargs:[true], scope:this};
686             if (fileinfo.type == 'folder') {
687                 if (!newfilename) {
688                     this.print_msg(M.util.get_string('entername', 'repository'), 'error');
689                     return;
690                 }
691                 if (filenamechanged || filepathchanged) {
692                     if (!confirmed) {
693                         dialog_options.message = M.util.get_string('confirmrenamefolder', 'repository');
694                         this.show_confirm_dialog(dialog_options);
695                         return;
696                     }
697                     params = {filepath:fileinfo.filepath, newdirname:newfilename, newfilepath:targetpath};
698                     action = 'updatedir';
699                 }
700             } else {
701                 if (!newfilename) {
702                     this.print_msg(M.util.get_string('enternewname', 'repository'), 'error');
703                     return;
704                 }
705                 if ((filenamechanged || filepathchanged) && !confirmed && fileinfo.refcount) {
706                     dialog_options.message = M.util.get_string('confirmrenamefile', 'repository', fileinfo.refcount);
707                     this.show_confirm_dialog(dialog_options);
708                     return;
709                 }
710                 if (filenamechanged || filepathchanged || licensechanged || authorchanged) {
711                     params = {filepath:fileinfo.filepath, filename:fileinfo.fullname,
712                         newfilename:newfilename, newfilepath:targetpath,
713                         newlicense:newlicense, newauthor:newauthor};
714                     action = 'updatefile';
715                 }
716             }
717             if (!action) {
718                 // no changes
719                 this.selectui.hide();
720                 return;
721             }
722             selectnode.addClass('loading');
723             this.request({
724                 action: action,
725                 scope: this,
726                 params: params,
727                 callback: function(id, obj, args) {
728                     if (obj.error) {
729                         selectnode.removeClass('loading');
730                         args.scope.print_msg(obj.error, 'error');
731                     } else {
732                         args.scope.selectui.hide();
733                         args.scope.refresh((obj && obj.filepath) ? obj.filepath : '/');
734                         if (typeof M.core_formchangechecker != 'undefined') {
735                             M.core_formchangechecker.set_form_changed();
736                         }
737                     }
738                 }
739             });
740         },
741         /**
742          * Displays a confirmation dialog
743          * Expected attributes in dialog_options: message, callback, callbackargs(optional), scope(optional)
744          */
745         show_confirm_dialog: function(dialog_options) {
746             // instead of M.util.show_confirm_dialog(e, dialog_options);
747             if (!this.confirm_dlg) {
748                 this.confirm_dlg_node = Y.Node.createWithFilesSkin(M.form_filemanager.templates.confirmdialog);
749                 var node = this.confirm_dlg_node;
750                 node.generateID();
751                 this.confirm_dlg = new M.core.dialogue({
752                     draggable    : true,
753                     bodyContent  : node,
754                     centered     : true,
755                     modal        : true,
756                     visible      : false,
757                     buttons      : {}
758                 });
759                 var handle_confirm = function(ev) {
760                     var dlgopt = this.confirm_dlg.dlgopt;
761                     ev.preventDefault();
762                     this.confirm_dlg.hide();
763                     if (dlgopt.callback) {
764                         if (dlgopt.callbackargs) {
765                             dlgopt.callback.apply(dlgopt.scope || this, dlgopt.callbackargs);
766                         } else {
767                             dlgopt.callback.apply(dlgopt.scope || this);
768                         }
769                     }
770                 }
771                 var handle_cancel = function(ev) {
772                     ev.preventDefault();
773                     this.confirm_dlg.hide();
774                 }
775                 node.one('.fp-dlg-butconfirm').on('click', handle_confirm, this);
776                 node.one('.fp-dlg-butcancel').on('click', handle_cancel, this);
777             }
778             this.confirm_dlg.dlgopt = dialog_options;
779             this.confirm_dlg_node.one('.fp-dlg-text').setContent(dialog_options.message);
780             this.confirm_dlg.show();
781         },
782         setup_select_file: function() {
783             var selectnode = this.selectnode;
784             // bind labels with corresponding inputs
785             selectnode.all('.fp-saveas,.fp-path,.fp-author,.fp-license').each(function (node) {
786                 node.all('label').set('for', node.one('input,select').generateID());
787             });
788             this.populate_licenses_select(selectnode.one('.fp-license select'));
789             // register event on clicking buttons
790             selectnode.one('.fp-file-update').on('click', function(e) {
791                 e.preventDefault();
792                 this.update_file();
793             }, this);
794             selectnode.all('form').on('keydown', function(e) {
795                 if (e.keyCode == 13) {
796                     e.preventDefault();
797                     this.update_file();
798                 }
799             }, this);
800             selectnode.one('.fp-file-download').on('click', function(e) {
801                 e.preventDefault();
802                 if (this.selectui.fileinfo.type != 'folder') {
803                     node = Y.Node.create('<iframe></iframe>').setStyles({
804                         visibility : 'hidden',
805                         width : '1px',
806                         height : '1px'
807                     });
808                     node.set('src', this.selectui.fileinfo.url);
809                     Y.one('body').appendChild(node);
810                 }
811             }, this);
812             selectnode.one('.fp-file-delete').on('click', function(e) {
813                 e.preventDefault();
814                 var dialog_options = {};
815                 var params = {};
816                 var fileinfo = this.selectui.fileinfo;
817                 dialog_options.scope = this;
818                 params.filepath = fileinfo.filepath;
819                 if (fileinfo.type == 'folder') {
820                     params.filename = '.';
821                     dialog_options.message = M.util.get_string('confirmdeletefolder', 'repository');
822                 } else {
823                     params.filename = fileinfo.fullname;
824                     if (fileinfo.refcount) {
825                         dialog_options.message = M.util.get_string('confirmdeletefilewithhref', 'repository', fileinfo.refcount);
826                     } else {
827                         dialog_options.message = M.util.get_string('confirmdeletefile', 'repository');
828                     }
829                 }
830                 dialog_options.callbackargs = [params];
831                 dialog_options.callback = function(params) {
832                     //selectnode.addClass('loading');
833                     this.request({
834                         action: 'delete',
835                         scope: this,
836                         params: params,
837                         callback: function(id, obj, args) {
838                             //args.scope.selectui.hide();
839                             args.scope.filecount--;
840                             args.scope.refresh(obj.filepath);
841                             if (typeof M.core_formchangechecker != 'undefined') {
842                                 M.core_formchangechecker.set_form_changed();
843                             }
844                         }
845                     });
846                 };
847                 this.selectui.hide(); // TODO remove this after confirm dialog is replaced with YUI3
848                 this.show_confirm_dialog(dialog_options);
849             }, this);
850             selectnode.one('.fp-file-zip').on('click', function(e) {
851                 e.preventDefault();
852                 var params = {};
853                 var fileinfo = this.selectui.fileinfo;
854                 if (fileinfo.type != 'folder') {
855                     // this button should not even be shown
856                     return;
857                 }
858                 params['filepath']   = fileinfo.filepath;
859                 params['filename']   = '.';
860                 selectnode.addClass('loading');
861                 this.request({
862                     action: 'zip',
863                     scope: this,
864                     params: params,
865                     callback: function(id, obj, args) {
866                         args.scope.selectui.hide();
867                         args.scope.refresh(obj.filepath);
868                     }
869                 });
870             }, this);
871             selectnode.one('.fp-file-unzip').on('click', function(e) {
872                 e.preventDefault();
873                 var params = {};
874                 var fileinfo = this.selectui.fileinfo;
875                 if (fileinfo.type != 'zip') {
876                     // this button should not even be shown
877                     return;
878                 }
879                 params['filepath'] = fileinfo.filepath;
880                 params['filename'] = fileinfo.fullname;
881                 selectnode.addClass('loading');
882                 this.request({
883                     action: 'unzip',
884                     scope: this,
885                     params: params,
886                     callback: function(id, obj, args) {
887                         args.scope.selectui.hide();
888                         args.scope.refresh(obj.filepath);
889                     }
890                 });
891             }, this);
892             selectnode.one('.fp-file-setmain').on('click', function(e) {
893                 e.preventDefault();
894                 var params = {};
895                 var fileinfo = this.selectui.fileinfo;
896                 if (!this.enablemainfile || fileinfo.type == 'folder') {
897                     // this button should not even be shown for folders or when mainfile is disabled
898                     return;
899                 }
900                 params['filepath'] = fileinfo.filepath;
901                 params['filename'] = fileinfo.fullname;
902                 selectnode.addClass('loading');
903                 this.request({
904                     action: 'setmainfile',
905                     scope: this,
906                     params: params,
907                     callback: function(id, obj, args) {
908                         args.scope.selectui.hide();
909                         args.scope.refresh(fileinfo.filepath);
910                     }
911                 });
912             }, this);
913             selectnode.all('.fp-file-cancel').on('click', function(e) {
914                 e.preventDefault();
915                 // TODO if changed asked to confirm, the same with close button
916                 this.selectui.hide();
917             }, this);
918         },
919         get_parent_folder_name: function(node) {
920             if (node.type != 'folder' || node.filepath.length < node.fullname.length+1) {
921                 return node.filepath;
922             }
923             var basedir = node.filepath.substr(0, node.filepath.length - node.fullname.length - 1);
924             var lastdir = node.filepath.substr(node.filepath.length - node.fullname.length - 2);
925             if (lastdir == '/' + node.fullname + '/') {
926                 return basedir;
927             }
928             return node.filepath;
929         },
930         select_file: function(node) {
931             if (this.is_disabled()) {
932                 return;
933             }
934             var selectnode = this.selectnode;
935             selectnode.removeClass('loading').removeClass('fp-folder').
936                 removeClass('fp-file').removeClass('fp-zip').removeClass('fp-cansetmain');
937             if (node.type == 'folder' || node.type == 'zip') {
938                 selectnode.addClass('fp-'+node.type);
939             } else {
940                 selectnode.addClass('fp-file');
941             }
942             if (this.enablemainfile && (node.sortorder != 1) && node.type == 'file') {
943                 selectnode.addClass('fp-cansetmain');
944             }
945             this.selectui.fileinfo = node;
946             selectnode.one('.fp-saveas input').set('value', node.fullname);
947             var foldername = this.get_parent_folder_name(node);
948             selectnode.all('.fp-author input').set('value', node.author ? node.author : '');
949             selectnode.all('.fp-license select option[selected]').set('selected', false);
950             selectnode.all('.fp-license select option[value='+node.license+']').set('selected', true);
951             selectnode.all('.fp-path select option[selected]').set('selected', false);
952             selectnode.all('.fp-path select option').each(function(el){
953                 if (el.get('value') == foldername) {
954                     el.set('selected', true);
955                 }
956             });
957             selectnode.all('.fp-author input, .fp-license select').set('disabled',(node.type == 'folder')?'disabled':'');
958             // display static information about a file (when known)
959             var attrs = ['datemodified','datecreated','size','dimensions','original','reflist'];
960             for (var i in attrs) {
961                 if (selectnode.one('.fp-'+attrs[i])) {
962                     var value = (node[attrs[i]+'_f']) ? node[attrs[i]+'_f'] : (node[attrs[i]] ? node[attrs[i]] : '');
963                     selectnode.one('.fp-'+attrs[i]).addClassIf('fp-unknown', ''+value == '')
964                         .one('.fp-value').setContent(Y.Escape.html(value));
965                 }
966             }
967             // display thumbnail
968             var imgnode = Y.Node.create('<img/>').
969                 set('src', node.realthumbnail ? node.realthumbnail : node.thumbnail).
970                 setStyle('maxHeight', ''+(node.thumbnail_height ? node.thumbnail_height : 90)+'px').
971                 setStyle('maxWidth', ''+(node.thumbnail_width ? node.thumbnail_width : 90)+'px');
972             selectnode.one('.fp-thumbnail').setContent('').appendChild(imgnode);
973             // load original location if applicable
974             if (node.isref && !node.original) {
975                 selectnode.one('.fp-original').removeClass('fp-unknown').addClass('fp-loading');
976                 this.request({
977                     action: 'getoriginal',
978                     scope: this,
979                     params: {'filepath':node.filepath,'filename':node.fullname},
980                     callback: function(id, obj, args) {
981                         // check if we did not select another file meanwhile
982                         var scope = args.scope;
983                         if (scope.selectui.fileinfo && node &&
984                                 scope.selectui.fileinfo.filepath == node.filepath &&
985                                 scope.selectui.fileinfo.fullname == node.fullname) {
986                             selectnode.one('.fp-original').removeClass('fp-loading');
987                             if (obj.original) {
988                                 node.original = obj.original;
989                                 selectnode.one('.fp-original .fp-value').setContent(Y.Escape.html(node.original));
990                             } else {
991                                 selectnode.one('.fp-original .fp-value').setContent(M.util.get_string('unknownsource', 'repository'));
992                             }
993                         }
994                     }
995                 }, false);
996             }
997             // load references list if applicable
998             selectnode.one('.fp-refcount').setContent(node.refcount ? M.util.get_string('referencesexist', 'repository', node.refcount) : '');
999             if (node.refcount && !node.reflist) {
1000                 selectnode.one('.fp-reflist').removeClass('fp-unknown').addClass('fp-loading');
1001                 this.request({
1002                     action: 'getreferences',
1003                     scope: this,
1004                     params: {'filepath':node.filepath,'filename':node.fullname},
1005                     callback: function(id, obj, args) {
1006                         // check if we did not select another file meanwhile
1007                         var scope = args.scope;
1008                         if (scope.selectui.fileinfo && node &&
1009                                 scope.selectui.fileinfo.filepath == node.filepath &&
1010                                 scope.selectui.fileinfo.fullname == node.fullname) {
1011                             selectnode.one('.fp-reflist').removeClass('fp-loading');
1012                             if (obj.references) {
1013                                 node.reflist = '';
1014                                 for (var i in obj.references) {
1015                                     node.reflist += '<li>'+Y.Escape.html(obj.references[i])+'</li>';
1016                                 }
1017                                 selectnode.one('.fp-reflist .fp-value').setContent(node.reflist);
1018                             } else {
1019                                 selectnode.one('.fp-reflist .fp-value').setContent('');
1020                             }
1021                         }
1022                     }
1023                 }, false);
1024             }
1025             // update dialog header
1026             var nodename = node.fullname;
1027             // Limit the string length so it fits nicely on mobile devices
1028             var namelength = 50;
1029             if (nodename.length > namelength) {
1030                 nodename = nodename.substring(0, namelength) + '...';
1031             }
1032             Y.one('#fm-dialog-label_'+selectnode.get('id')).setContent(Y.Escape.html(M.util.get_string('edit', 'moodle')+' '+nodename));
1033             // show panel
1034             this.selectui.show();
1035             Y.one('#'+selectnode.get('id')).focus();
1036         },
1037         render: function() {
1038             this.print_path();
1039             this.view_files();
1040         },
1041         has_folder: function(foldername) {
1042             var element;
1043             for (var i in this.options.list) {
1044                 element = this.options.list[i];
1045                 if (element.type == 'folder' && element.fullname == foldername) {
1046                     return true;
1047                 }
1048             }
1049             return false;
1050         }
1051     });
1053     // finally init everything needed
1054     // hide loading picture, display filemanager interface
1055     var filemanager = Y.one('#filemanager-'+options.client_id);
1056     filemanager.removeClass('fm-loading').addClass('fm-loaded');
1058     var manager = new FileManagerHelper(options);
1059     var dndoptions = {
1060         filemanager: manager,
1061         acceptedtypes: options.filepicker.accepted_types,
1062         clientid: options.client_id,
1063         author: options.author,
1064         maxfiles: options.maxfiles,
1065         maxbytes: options.maxbytes,
1066         areamaxbytes: options.areamaxbytes,
1067         itemid: options.itemid,
1068         repositories: manager.filepicker_options.repositories,
1069         containerid: manager.dndcontainer.get('id'),
1070         contextid: options.context.id
1071     };
1072     M.form_dndupload.init(Y, dndoptions);