1 // This file is part of Moodle - http://moodle.org/
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.
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/>.
19 * this.api, stores the URL to make ajax request
21 * this.filepicker_options
22 * this.movefile_dialog
26 * this.filecount, how many files in this filemanager
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 * this.viewmode, contains current view mode - icons, tree or details
35 * FileManager options:
37 * this.options.currentpath
41 /* eslint camelcase: off */
43 M.form_filemanager = {
45 formChangeChecker: null,
48 require(['core_form/changechecker'], function(FormChangeChecker) {
49 // This is a nasty, hacky, way of doing it but it's the smallest evil.
50 M.form_filemanager.formChangeChecker = FormChangeChecker;
53 M.form_filemanager.set_templates = function(Y, templates) {
54 M.form_filemanager.templates = templates;
58 * This fucntion is called for each file picker on page.
60 M.form_filemanager.init = function(Y, options) {
61 var FileManagerHelper = function(options) {
62 FileManagerHelper.superclass.constructor.apply(this, arguments);
64 FileManagerHelper.NAME = "FileManager";
65 FileManagerHelper.ATTRS = {
70 Y.extend(FileManagerHelper, Y.Base, {
71 api: M.cfg.wwwroot+'/repository/draftfiles_ajax.php',
73 initializer: function(options) {
74 this.options = options;
75 if (options.mainfile) {
76 this.enablemainfile = options.mainfile;
78 this.client_id = options.client_id;
79 this.currentpath = '/';
80 this.maxfiles = options.maxfiles;
81 this.maxbytes = options.maxbytes;
82 this.areamaxbytes = options.areamaxbytes;
83 this.userprefs = options.userprefs;
84 this.emptycallback = null; // Used by drag and drop upload
86 this.filepicker_options = options.filepicker?options.filepicker:{};
87 this.filepicker_options.client_id = this.client_id;
88 this.filepicker_options.context = options.context;
89 this.filepicker_options.maxfiles = this.maxfiles;
90 this.filepicker_options.maxbytes = this.maxbytes;
91 this.filepicker_options.areamaxbytes = this.areamaxbytes;
92 this.filepicker_options.env = 'filemanager';
93 this.filepicker_options.itemid = options.itemid;
95 if (options.filecount) {
96 this.filecount = options.filecount;
100 // prepare filemanager for drag-and-drop upload
101 this.filemanager = Y.one('#filemanager-'+options.client_id);
102 if (this.filemanager.hasClass('filemanager-container') || !this.filemanager.one('.filemanager-container')) {
103 this.dndcontainer = this.filemanager;
105 this.dndcontainer = this.filemanager.one('.filemanager-container');
106 if (!this.dndcontainer.get('id')) {
107 this.dndcontainer.generateID();
110 // save template for one path element and location of path bar
111 if (this.filemanager.one('.fp-path-folder')) {
112 this.pathnode = this.filemanager.one('.fp-path-folder');
113 this.pathbar = this.pathnode.get('parentNode');
114 this.pathbar.removeChild(this.pathnode);
116 // initialize 'select file' panel
117 this.selectnode = Y.Node.create(M.form_filemanager.templates.fileselectlayout);
118 this.selectnode.setAttribute('aria-live', 'assertive');
119 this.selectnode.setAttribute('role', 'dialog');
120 this.selectnode.generateID();
122 var labelid = 'fm-dialog-label_'+ this.selectnode.get('id');
123 this.selectui = new M.core.dialogue({
125 headerContent: '<h3 id="' + labelid +'">' + M.util.get_string('edit', 'moodle') + '</h3>',
126 bodyContent : this.selectnode,
132 Y.one('#'+this.selectnode.get('id')).setAttribute('aria-labelledby', labelid);
133 this.selectui.hide();
134 this.setup_select_file();
135 // setup buttons onclick events
136 this.setup_buttons();
137 // set event handler for lazy loading of thumbnails
138 this.filemanager.one('.fp-content').on(['scroll','resize'], this.content_scrolled, this);
140 this.viewmode = this.get_preference("recentviewmode");
141 if (this.viewmode != 2 && this.viewmode != 3) {
144 var viewmodeselectors = {'1': '.fp-vb-icons', '2': '.fp-vb-tree', '3': '.fp-vb-details'};
145 this.filemanager.all('.fp-vb-icons,.fp-vb-tree,.fp-vb-details').removeClass('checked');
146 this.filemanager.all(viewmodeselectors[this.viewmode]).addClass('checked');
147 this.refresh(this.currentpath); // MDL-31113 get latest list from server
151 this.filemanager.addClass('fm-updating');
153 request: function(args, redraw) {
154 var api = this.api + '?action='+args.action;
158 scope = args['scope'];
160 params['sesskey'] = M.cfg.sesskey;
161 params['client_id'] = this.client_id;
162 params['filepath'] = this.currentpath;
163 params['itemid'] = this.options.itemid?this.options.itemid:0;
164 if (args['params']) {
165 for (i in args['params']) {
166 params[i] = args['params'][i];
172 complete: function(id,o,p) {
179 data = Y.JSON.parse(o.responseText);
181 scope.print_msg(M.util.get_string('invalidjson', 'repository'), 'error');
182 Y.error(M.util.get_string('invalidjson', 'repository')+":\n"+o.responseText);
185 if (data && data.tree && scope.set_current_tree) {
186 scope.set_current_tree(data.tree);
188 args.callback(id,data,p);
195 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
197 data: build_querystring(params)
200 cfg.form = args.form;
207 filepicker_callback: function(obj) {
209 this.check_buttons();
210 this.refresh(this.currentpath);
211 M.form_filemanager.formChangeChecker.markFormChangedFromNode(this.filemanager.getDOMNode());
213 require(['core_form/events'], function(FormEvent) {
214 FormEvent.notifyUploadChanged(this.filemanager.get('id'));
217 check_buttons: function() {
218 if (this.filecount>0) {
219 this.filemanager.removeClass('fm-nofiles');
221 this.filemanager.addClass('fm-nofiles');
223 if (this.filecount >= this.maxfiles && this.maxfiles!=-1) {
224 this.filemanager.addClass('fm-maxfiles');
227 this.filemanager.removeClass('fm-maxfiles');
230 refresh: function(filepath, action) {
232 this.currentpath = filepath;
234 filepath = this.currentpath;
236 this.currentpath = filepath;
241 params: {'filepath':filepath},
242 callback: function(id, obj, args) {
243 scope.filecount = obj.filecount;
245 scope.lazyloading = {};
246 scope.check_buttons();
247 scope.render(obj, action);
251 /** displays message in a popup */
252 print_msg: function(msg, type, options) {
253 var header = M.util.get_string('error', 'moodle');
254 if (type != 'error') {
255 type = 'info'; // one of only two types excepted
256 header = M.util.get_string('info', 'moodle');
259 this.msg_dlg_node = Y.Node.create(M.form_filemanager.templates.message);
260 var nodeid = this.msg_dlg_node.generateID();
261 var previousActiveElement = null;
262 if (typeof options.previousActiveElement != 'undefined') {
263 previousActiveElement = options.previousActiveElement;
265 this.msg_dlg = new M.core.dialogue({
267 bodyContent : this.msg_dlg_node,
271 focusAfterHide: previousActiveElement,
273 this.msg_dlg_node.one('.fp-msg-butok').on('click', function(e) {
279 this.msg_dlg.set('headerContent', header);
280 this.msg_dlg_node.removeClass('fp-msg-info').removeClass('fp-msg-error').addClass('fp-msg-'+type)
281 this.msg_dlg_node.one('.fp-msg-text').setContent(Y.Escape.html(msg));
284 is_disabled: function() {
285 return this.filemanager.ancestor('.fitem.disabled') != null;
287 getSelectedFiles: function() {
288 var markedFiles = this.filemanager.all('[data-togglegroup=file-selections]:checked');
290 markedFiles.each(function(item) {
291 var fileinfo = this.options.list.find(function(element) {
292 return item.getData().fullname == element.fullname;
294 if (fileinfo && fileinfo != undefined) {
296 filepath: fileinfo.filepath,
297 filename: fileinfo.filename
304 setup_buttons: function() {
305 var button_download = this.filemanager.one('.fp-btn-download');
306 var button_create = this.filemanager.one('.fp-btn-mkdir');
307 var button_addfile = this.filemanager.one('.fp-btn-add');
308 var buttonDeleteFile = this.filemanager.one('.fp-btn-delete');
310 // setup 'add file' button
311 button_addfile.on('click', this.show_filepicker, this);
313 var dndarrow = this.filemanager.one('.dndupload-arrow');
315 dndarrow.on('click', this.show_filepicker, this);
318 // setup 'make a folder' button
319 if (this.options.subdirs) {
320 button_create.on('click',function(e) {
322 if (this.is_disabled()) {
326 // a function used to perform an ajax request
327 var perform_action = function(e) {
329 var foldername = Y.one('#fm-newname-'+scope.client_id).get('value');
331 scope.mkdir_dialog.hide();
336 params: {filepath:scope.currentpath, newdirname:foldername},
337 callback: function(id, obj, args) {
338 var filepath = obj.filepath;
339 scope.mkdir_dialog.hide();
340 scope.refresh(filepath);
341 Y.one('#fm-newname-'+scope.client_id).set('value', '');
342 M.form_filemanager.formChangeChecker.markFormChangedFromNode(scope.filemanager.getDOMNode());
346 var validate_folder_name = function() {
348 var foldername = Y.one('#fm-newname-'+scope.client_id).get('value');
349 if (foldername.length > 0) {
352 var btn = Y.one('#fm-mkdir-butcreate-'+scope.client_id);
354 btn.set('disabled', !valid);
358 if (!this.mkdir_dialog) {
359 var node = Y.Node.create(M.form_filemanager.templates.mkdir);
360 this.mkdir_dialog = new M.core.dialogue({
366 focusAfterHide: e.target.ancestor('a', true),
368 node.one('.fp-dlg-butcreate').set('id', 'fm-mkdir-butcreate-'+this.client_id).on('click',
369 perform_action, this);
370 node.one('input').set('id', 'fm-newname-'+this.client_id).on('keydown', function(e) {
371 var valid = Y.bind(validate_folder_name, this)();
372 if (valid && e.keyCode === 13) {
373 Y.bind(perform_action, this)(e);
376 node.one('#fm-newname-'+this.client_id).on(['keyup', 'change'], function(e) {
377 Y.bind(validate_folder_name, this)();
380 node.one('label').set('for', 'fm-newname-' + this.client_id);
381 node.all('.fp-dlg-butcancel').on('click', function(e){e.preventDefault();this.mkdir_dialog.hide();}, this);
382 node.all('.fp-dlg-curpath').set('id', 'fm-curpath-'+this.client_id);
384 this.mkdir_dialog.show();
386 // Default folder name:
387 var foldername = M.util.get_string('newfolder', 'repository');
388 while (this.has_folder(foldername)) {
389 foldername = increment_filename(foldername, true);
391 Y.one('#fm-newname-'+scope.client_id).set('value', foldername);
392 Y.bind(validate_folder_name, this)();
393 Y.one('#fm-newname-'+scope.client_id).focus().select();
394 Y.all('#fm-curpath-'+scope.client_id).setContent(this.currentpath);
397 this.filemanager.addClass('fm-nomkdir');
400 // setup 'download this folder' button
401 button_download.on('click',function(e) {
403 if (this.is_disabled()) {
408 var image_downloading = this.filemanager.one('.fp-img-downloading');
409 if (image_downloading.getStyle('display') == 'inline') {
412 image_downloading.setStyle('display', 'inline');
413 var filenames = this.getSelectedFiles();
415 // perform downloaddir ajax request
417 action: 'downloadselected',
419 params: {selected: Y.JSON.stringify(filenames)},
420 callback: function(id, obj, args) {
421 var image_downloading = scope.filemanager.one('.fp-img-downloading');
422 image_downloading.setStyle('display', 'none');
425 scope.refresh(obj.filepath);
426 node = Y.Node.create('<iframe></iframe>').setStyles({
427 visibility : 'hidden',
431 node.set('src', obj.fileurl);
432 Y.one('body').appendChild(node);
434 scope.print_msg(M.util.get_string('draftareanofiles', 'repository'), 'error');
440 buttonDeleteFile.on('click', function(e) {
442 var dialogOptions = {};
443 var filenames = this.getSelectedFiles();
444 var previousActiveElement = e.target.ancestor('a', true);
446 if (!filenames.length) {
448 options.previousActiveElement = previousActiveElement;
449 this.print_msg(M.util.get_string('nofilesselected', 'repository'), 'error', options);
453 dialogOptions.scope = this;
455 selected: Y.JSON.stringify(filenames)
457 dialogOptions.header = M.util.get_string('confirm', 'moodle');
458 dialogOptions.message = M.util.get_string('confirmdeleteselectedfile', 'repository', filenames.length);
459 dialogOptions.previousActiveElement = previousActiveElement;
460 dialogOptions.callbackargs = [params];
461 dialogOptions.callback = function(params) {
463 action: 'deleteselected',
466 callback: function(id, obj, args) {
468 args.scope.filecount -= params.length;
469 if (obj && obj.length) {
470 args.scope.refresh(obj[0], {action: 'delete'});
472 M.form_filemanager.formChangeChecker.markFormChangedFromNode(this.scope.filemanager.getDOMNode());
474 require(['core_form/events'], function(FormEvent) {
475 FormEvent.notifyUploadChanged(this.scope.filemanager.get('id'));
480 this.show_confirm_dialog(dialogOptions);
483 this.filemanager.all('.fp-vb-icons,.fp-vb-tree,.fp-vb-details').
484 on('click', function(e) {
486 var viewbar = this.filemanager.one('.fp-viewbar')
487 if (!this.is_disabled() && (!viewbar || !viewbar.hasClass('disabled'))) {
488 this.filemanager.all('.fp-vb-icons,.fp-vb-tree,.fp-vb-details').removeClass('checked')
489 if (e.currentTarget.hasClass('fp-vb-tree')) {
491 } else if (e.currentTarget.hasClass('fp-vb-details')) {
496 e.currentTarget.addClass('checked')
498 this.filemanager.one('.fp-content').setAttribute('tabIndex', '0');
499 this.filemanager.one('.fp-content').focus();
500 this.set_preference('recentviewmode', this.viewmode);
505 show_filepicker: function (e) {
506 // if maxfiles == -1, the no limit
508 if (this.is_disabled()) {
511 var options = this.filepicker_options;
512 options.formcallback = this.filepicker_callback;
513 // XXX: magic here, to let filepicker use filemanager scope
514 options.magicscope = this;
515 options.savepath = this.currentpath;
516 options.previousActiveElement = e.target.ancestor('a', true);
517 M.core_filepicker.show(Y, options);
520 print_path: function() {
521 var p = this.options.path;
522 this.pathbar.setContent('').addClass('empty');
523 if (p && p.length!=0 && this.viewmode != 2) {
524 for(var i = 0; i < p.length; i++) {
525 var el = this.pathnode.cloneNode(true);
526 this.pathbar.appendChild(el);
529 el.addClass('first');
531 if (i == p.length-1) {
540 el.one('.fp-path-folder-name').setContent(Y.Escape.html(p[i].name)).
541 on('click', function(e, path) {
543 if (!this.is_disabled()) {
548 this.pathbar.removeClass('empty');
551 get_filepath: function(obj) {
552 if (obj.path && obj.path.length) {
553 return obj.path[obj.path.length-1].path;
557 treeview_dynload: function(node, cb) {
558 var retrieved_children = {};
560 for (var i in node.children) {
561 retrieved_children[node.children[i].path] = node.children[i];
564 if (!node.path || node.path == '/') {
565 // this is a root pseudo folder
566 node.fileinfo.filepath = '/';
567 node.fileinfo.type = 'folder';
568 node.fileinfo.fullname = node.fileinfo.title;
569 node.fileinfo.filename = '.';
573 params: {filepath:node.path?node.path:''},
575 callback: function(id, obj, args) {
577 var scope = args.scope;
578 // check that user did not leave the view mode before recieving this response
579 if (!(scope.viewmode == 2 && node && node.getChildrenEl())) {
582 if (cb != null) { // (in manual mode do not update current path)
584 scope.currentpath = node.path?node.path:'/';
586 node.highlight(false);
587 node.origlist = obj.list ? obj.list : null;
588 node.origpath = obj.path ? obj.path : null;
591 if (list[k].type == 'folder' && retrieved_children[list[k].filepath]) {
592 // if this child is a folder and has already been retrieved
593 retrieved_children[list[k].filepath].fileinfo = list[k];
594 node.children[node.children.length] = retrieved_children[list[k].filepath];
596 // append new file to the list
597 scope.view_files([list[k]]);
603 // invoke callback requested by TreeView component
606 scope.content_scrolled();
610 content_scrolled: function(e) {
611 setTimeout(Y.bind(function() {
612 if (this.processingimages) {return;}
613 this.processingimages = true;
615 fpcontent = this.filemanager.one('.fp-content'),
616 fpcontenty = fpcontent.getY(),
617 fpcontentheight = fpcontent.getStylePx('height'),
618 is_node_visible = function(node) {
619 var offset = node.getY()-fpcontenty;
620 if (offset <= fpcontentheight && (offset >=0 || offset+node.getStylePx('height')>=0)) {
625 // replace src for visible images that need to be lazy-loaded
626 if (scope.lazyloading) {
627 fpcontent.all('img').each( function(node) {
628 if (node.get('id') && scope.lazyloading[node.get('id')] && is_node_visible(node)) {
629 node.setImgRealSrc(scope.lazyloading);
633 this.processingimages = false;
636 view_files: function(appendfiles, actionfiles) {
637 this.filemanager.removeClass('fm-updating').removeClass('fm-noitems');
638 if ((appendfiles == null) && (!this.options.list || this.options.list.length == 0) && this.viewmode != 2) {
639 this.filemanager.addClass('fm-noitems');
640 // This is used to focus after refreshing the list files is empty by deletion file action.
641 if (actionfiles !== undefined && actionfiles.action == 'delete') {
642 this.filemanager.one('.fp-btn-add a').focus();
646 var list = (appendfiles != null) ? appendfiles : this.options.list;
647 var element_template;
648 if (this.viewmode == 2 || this.viewmode == 3) {
649 element_template = Y.Node.create(M.form_filemanager.templates.listfilename);
652 element_template = Y.Node.create(M.form_filemanager.templates.iconfilename);
655 if (this.viewmode == 1 || this.viewmode == 2) {
656 this.filemanager.one('.fp-btn-delete').addClass('d-none');
658 this.filemanager.one('.fp-btn-delete').removeClass('d-none');
661 viewmode : this.viewmode,
662 appendonly : appendfiles != null,
663 filenode : element_template,
664 disablecheckboxes: false,
665 callbackcontext : this,
666 callback : function(e, node) {
667 if (e.preventDefault) { e.preventDefault(); }
668 if (node.type == 'folder') {
669 this.refresh(node.filepath);
671 // This is used to focus on file after dialogue closed.
672 var previousActiveElement = e.target.ancestor('a', true);
673 this.options.previousActiveElement = previousActiveElement;
674 this.selectui.set('focusOnPreviousTargetAfterHide', true);
675 this.selectui.set('focusAfterHide', previousActiveElement);
676 this.select_file(node);
679 rightclickcallback : function(e, node) {
680 if (e.preventDefault) { e.preventDefault(); }
681 this.select_file(node);
683 classnamecallback : function(node) {
685 if (node.type == 'folder' || (!node.type && !node.filename)) {
686 classname = classname + ' fp-folder';
688 if (node.filename || node.filepath || (node.path && node.path != '/')) {
689 classname = classname + ' fp-hascontextmenu';
692 classname = classname + ' fp-isreference';
695 classname = classname + ' fp-hasreferences';
697 if (node.originalmissing) {
698 classname = classname + ' fp-originalmissing';
700 if (node.sortorder == 1) { classname = classname + ' fp-mainfile';}
701 return Y.Lang.trim(classname);
704 if (this.viewmode == 2) {
705 options.dynload = true;
706 options.filepath = this.options.path;
707 options.treeview_dynload = this.treeview_dynload;
708 options.norootrightclick = true;
709 options.callback = function(e, node) {
710 // TODO MDL-32736 e is not an event here but an object with properties 'event' and 'node'
711 if (!node.fullname) {return;}
712 if (node.type != 'folder') {
713 if (e.node.parent && e.node.parent.origpath) {
714 // set the current path
715 this.options.path = e.node.parent.origpath;
716 this.options.list = e.node.parent.origlist;
719 this.currentpath = node.filepath;
720 var previousActiveElement = Y.Node(e.event.target).ancestor('a', true);
721 this.options.previousActiveElement = previousActiveElement;
722 this.selectui.set('focusOnPreviousTargetAfterHide', true);
723 this.selectui.set('focusAfterHide', previousActiveElement);
724 this.select_file(node);
726 // save current path and filelist (in case we want to jump to other viewmode)
727 this.options.path = e.node.origpath;
728 this.options.list = e.node.origlist;
729 this.currentpath = node.filepath;
731 //this.content_scrolled();
735 if (!this.lazyloading) {
738 this.filemanager.one('.fp-content').fp_display_filelist(options, list, this.lazyloading);
739 if (this.viewmode != 2) {
740 this.content_scrolled();
742 // This is used to focus after refreshing the list files by update file and set main file action.
743 if (actionfiles !== undefined) {
744 if (actionfiles.action == 'updatefile' || actionfiles.action == 'setmainfile') {
745 var fileslist = this.filemanager.one('.fp-content');
746 fileslist.all('a').each(function(parentnode) {
747 parentnode.all('.fp-filename').each(function(childnode) {
748 if (childnode.get('innerHTML') == actionfiles.newfilename) {
754 if (actionfiles.action == 'delete') {
755 this.filemanager.one('.fp-btn-delete a').focus();
759 populateLicensesSelect: function(licensenode, filenode) {
763 licensenode.setContent('');
764 var selectedlicense = this.filepicker_options.defaultlicense;
766 // File has a license already, use it.
767 selectedlicense = filenode.license;
768 } else if (this.filepicker_options.rememberuserlicensepref && this.get_preference('recentlicense')) {
769 // When 'Remember user licence preference' is enabled use the last license selected by the user, if any.
770 selectedlicense = this.get_preference('recentlicense');
772 var licenses = this.filepicker_options.licenses;
773 for (var i in licenses) {
774 // Include the file's current license, even if not enabled, to prevent displaying
775 // misleading information about which license the file currently has assigned to it.
776 if (licenses[i].enabled == true || (filenode !== undefined && licenses[i].shortname === filenode.license)) {
777 var option = Y.Node.create('<option/>').
778 set('selected', (licenses[i].shortname == selectedlicense)).
779 set('value', licenses[i].shortname).
780 setContent(Y.Escape.html(licenses[i].fullname));
781 licensenode.appendChild(option);
785 set_current_tree: function(tree) {
786 var appendfilepaths = function(list, node) {
787 if (!node || !node.children || !node.children.length) {return;}
788 for (var i in node.children) {
789 list[list.length] = node.children[i].filepath;
790 appendfilepaths(list, node.children[i]);
794 appendfilepaths(list, tree);
795 var selectnode = this.selectnode;
796 node = selectnode.one('.fp-path select');
798 for (var i in list) {
799 node.appendChild(Y.Node.create('<option/>').
800 set('value', list[i]).setContent(Y.Escape.html(list[i])));
803 update_file: function(confirmed) {
804 var selectnode = this.selectnode;
805 var fileinfo = this.selectui.fileinfo;
807 var newfilename = Y.Lang.trim(selectnode.one('.fp-saveas input').get('value'));
808 var filenamechanged = (newfilename && newfilename != fileinfo.fullname);
809 var pathselect = selectnode.one('.fp-path select'),
810 pathindex = pathselect.get('selectedIndex'),
811 targetpath = pathselect.get("options").item(pathindex).get('value');
812 var filepathchanged = (targetpath != this.get_parent_folder_name(fileinfo));
813 var newauthor = Y.Lang.trim(selectnode.one('.fp-author input').get('value'));
814 var authorchanged = (newauthor != Y.Lang.trim(fileinfo.author));
815 var licenseselect = selectnode.one('.fp-license select'),
816 licenseindex = licenseselect.get('selectedIndex'),
817 newlicense = licenseselect.get("options").item(licenseindex).get('value');
818 var licensechanged = (newlicense != fileinfo.license);
821 var dialog_options = {callback:this.update_file, callbackargs:[true], scope:this};
822 if (fileinfo.type == 'folder') {
824 this.print_msg(M.util.get_string('entername', 'repository'), 'error');
827 if (filenamechanged || filepathchanged) {
829 dialog_options.message = M.util.get_string('confirmrenamefolder', 'repository');
830 this.show_confirm_dialog(dialog_options);
833 params = {filepath:fileinfo.filepath, newdirname:newfilename, newfilepath:targetpath};
834 action = 'updatedir';
838 this.print_msg(M.util.get_string('enternewname', 'repository'), 'error');
842 if ((filenamechanged || filepathchanged) && !confirmed) {
844 var originalfilenamearr = fileinfo.fullname.split('.');
845 var originalextension = (originalfilenamearr.length > 1) ? originalfilenamearr.pop() : "";
846 var newfilenamearr = newfilename.split('.');
847 var newextension = (newfilenamearr.length > 1) ? newfilenamearr.pop() : "";
849 if (newextension !== originalextension) {
850 if (newextension === "") {
851 var string = M.util.get_string('originalextensionremove', 'repository', originalextension);
854 originalextension: originalextension,
855 newextension: newextension
857 string = M.util.get_string('originalextensionchange', 'repository', stringvars);
859 warnings = warnings.concat('<li>', string, '</li>');
861 if (fileinfo.refcount) {
862 var string = M.util.get_string('aliaseschange', 'repository', fileinfo.refcount);
863 warnings = warnings.concat('<li>', string, '</li>');
865 if (warnings.length > 0) {
867 var confirmmsg = M.util.get_string('confirmrenamefile', 'repository', fileinfo.refcount);
868 dialog_options.message = message.concat('<p>', confirmmsg, '</p>',
869 '<ul class="px-5">', warnings, '</ul>');
870 this.show_confirm_dialog(dialog_options);
874 if (filenamechanged || filepathchanged || licensechanged || authorchanged) {
875 params = {filepath:fileinfo.filepath, filename:fileinfo.fullname,
876 newfilename:newfilename, newfilepath:targetpath,
877 newlicense:newlicense, newauthor:newauthor};
878 action = 'updatefile';
883 this.selectui.hide();
886 selectnode.addClass('loading');
891 callback: function(id, obj, args) {
893 selectnode.removeClass('loading');
894 args.scope.print_msg(obj.error, 'error');
896 args.scope.selectui.hide();
897 var actionfile = {action: action, newfilename: newfilename};
898 args.scope.refresh((obj && obj.filepath) ? obj.filepath : '/', actionfile);
899 M.form_filemanager.formChangeChecker.markFormChangedFromNode(this.scope.filemanager.getDOMNode());
905 * Displays a confirmation dialog
906 * Expected attributes in dialog_options: message, callback, callbackargs(optional), scope(optional)
908 show_confirm_dialog: function(dialog_options) {
909 // instead of M.util.show_confirm_dialog(e, dialog_options);
910 if (!this.confirm_dlg) {
911 this.confirm_dlg_node = Y.Node.create(M.form_filemanager.templates.confirmdialog);
912 var node = this.confirm_dlg_node;
914 this.confirm_dlg = new M.core.dialogue({
922 var handle_confirm = function(ev) {
923 var dlgopt = this.confirm_dlg.dlgopt;
925 this.confirm_dlg.hide();
926 if (dlgopt.callback) {
927 if (dlgopt.callbackargs) {
928 dlgopt.callback.apply(dlgopt.scope || this, dlgopt.callbackargs);
930 dlgopt.callback.apply(dlgopt.scope || this);
934 var handle_cancel = function(ev) {
936 this.confirm_dlg.hide();
938 node.one('.fp-dlg-butconfirm').on('click', handle_confirm, this);
939 node.one('.fp-dlg-butcancel').on('click', handle_cancel, this);
941 // This used to focus on before active element after confirm dialogue closed.
942 if (typeof dialog_options.previousActiveElement != 'undefined') {
943 this.confirm_dlg.set('focusAfterHide', dialog_options.previousActiveElement);
945 this.confirm_dlg.dlgopt = dialog_options;
946 if (typeof dialog_options.header != 'undefined') {
947 this.confirm_dlg.set('headerContent', dialog_options.header);
949 this.confirm_dlg_node.one('.fp-dlg-text').setContent(dialog_options.message);
950 this.confirm_dlg.show();
952 setup_select_file: function() {
953 var selectnode = this.selectnode;
955 // bind labels with corresponding inputs
956 selectnode.all('.fp-saveas,.fp-path,.fp-author,.fp-license').each(function (node) {
957 node.all('label').set('for', node.one('input,select').generateID());
959 // register event on clicking buttons
960 selectnode.one('.fp-file-update').on('click', function(e) {
964 selectnode.all('form input').on('key', function(e) {
968 selectnode.one('.fp-file-download').on('click', function(e) {
970 if (this.selectui.fileinfo.type != 'folder') {
971 node = Y.Node.create('<iframe></iframe>').setStyles({
972 visibility : 'hidden',
976 node.set('src', this.selectui.fileinfo.url);
977 Y.one('body').appendChild(node);
980 selectnode.one('.fp-file-delete').on('click', function(e) {
982 var dialog_options = {
984 header: M.util.get_string('confirm', 'moodle'),
987 var fileinfo = this.selectui.fileinfo;
988 params.filepath = fileinfo.filepath;
989 if (fileinfo.type == 'folder') {
990 params.filename = '.';
991 dialog_options.message = M.util.get_string('confirmdeletefolder', 'repository');
993 params.filename = fileinfo.fullname;
994 if (fileinfo.refcount) {
995 dialog_options.message = M.util.get_string('confirmdeletefilewithhref', 'repository', fileinfo.refcount);
997 dialog_options.message = M.util.get_string('confirmdeletefile', 'repository');
1000 dialog_options.callbackargs = [params];
1001 dialog_options.callback = function(params) {
1002 //selectnode.addClass('loading');
1007 callback: function(id, obj, args) {
1008 //args.scope.selectui.hide();
1009 args.scope.filecount--;
1010 args.scope.refresh(obj.filepath, {action: 'delete'});
1011 M.form_filemanager.formChangeChecker.markFormChangedFromNode(this.scope.filemanager.getDOMNode());
1013 require(['core_form/events'], function(FormEvent) {
1014 FormEvent.notifyUploadChanged(this.scope.filemanager.get('id'));
1019 this.selectui.hide(); // TODO remove this after confirm dialog is replaced with YUI3
1020 // This is used to focus on before active element after confirm dialogue closed.
1021 if (this.options.previousActiveElement !== undefined) {
1022 dialog_options.previousActiveElement = this.options.previousActiveElement;
1024 this.show_confirm_dialog(dialog_options);
1026 selectnode.one('.fp-file-zip').on('click', function(e) {
1029 var fileinfo = this.selectui.fileinfo;
1030 if (fileinfo.type != 'folder') {
1031 // this button should not even be shown
1034 params['filepath'] = fileinfo.filepath;
1035 params['filename'] = '.';
1036 selectnode.addClass('loading');
1041 callback: function(id, obj, args) {
1042 args.scope.selectui.hide();
1043 args.scope.refresh(obj.filepath);
1047 selectnode.one('.fp-file-unzip').on('click', function(e) {
1050 var fileinfo = this.selectui.fileinfo;
1051 if (fileinfo.type != 'zip') {
1052 // this button should not even be shown
1055 params['filepath'] = fileinfo.filepath;
1056 params['filename'] = fileinfo.fullname;
1057 // The unlimited value of areamaxbytes is -1, it is defined by FILE_AREA_MAX_BYTES_UNLIMITED.
1058 params['areamaxbytes'] = this.areamaxbytes ? this.areamaxbytes : -1;
1059 selectnode.addClass('loading');
1064 callback: function(id, obj, args) {
1066 selectnode.removeClass('loading');
1067 args.scope.print_msg(obj.error, 'error');
1069 args.scope.selectui.hide();
1070 args.scope.refresh(obj.filepath);
1075 selectnode.one('.fp-file-setmain').on('click', function(e) {
1078 var fileinfo = this.selectui.fileinfo;
1079 if (!this.enablemainfile || fileinfo.type == 'folder') {
1080 // this button should not even be shown for folders or when mainfile is disabled
1083 params['filepath'] = fileinfo.filepath;
1084 params['filename'] = fileinfo.fullname;
1085 selectnode.addClass('loading');
1087 action: 'setmainfile',
1090 callback: function(id, obj, args) {
1091 args.scope.selectui.hide();
1092 var actionfile = {action: 'setmainfile', newfilename: fileinfo.fullname};
1093 args.scope.refresh(fileinfo.filepath, actionfile);
1097 selectnode.all('.fp-file-cancel').on('click', function(e) {
1099 // TODO if changed asked to confirm, the same with close button
1100 this.selectui.hide();
1102 selectnode.all('.fp-file-update, .fp-file-download, .fp-file-delete, .fp-file-zip, .fp-file-unzip, ' +
1103 '.fp-file-setmain, .fp-file-cancel').on('key', function(e) {
1105 this.simulate('click');
1108 get_parent_folder_name: function(node) {
1109 if (node.type != 'folder' || node.filepath.length < node.fullname.length+1) {
1110 return node.filepath;
1112 var basedir = node.filepath.substr(0, node.filepath.length - node.fullname.length - 1);
1113 var lastdir = node.filepath.substr(node.filepath.length - node.fullname.length - 2);
1114 if (lastdir == '/' + node.fullname + '/') {
1117 return node.filepath;
1119 select_file: function(node) {
1120 if (this.is_disabled()) {
1123 var selectnode = this.selectnode;
1124 selectnode.removeClass('loading').removeClass('fp-folder').
1125 removeClass('fp-file').removeClass('fp-zip').removeClass('fp-cansetmain');
1126 if (node.type == 'folder' || node.type == 'zip') {
1127 selectnode.addClass('fp-'+node.type);
1129 selectnode.addClass('fp-file');
1131 if (this.enablemainfile && (node.sortorder != 1) && node.type == 'file') {
1132 selectnode.addClass('fp-cansetmain');
1134 this.selectui.fileinfo = node;
1135 selectnode.one('.fp-saveas input').set('value', node.fullname);
1136 var foldername = this.get_parent_folder_name(node);
1137 selectnode.all('.fp-author input').set('value', node.author ? node.author : '');
1138 this.populateLicensesSelect(selectnode.one('.fp-license select'), node);
1139 selectnode.all('.fp-path select option[selected]').set('selected', false);
1140 selectnode.all('.fp-path select option').each(function(el){
1141 if (el.get('value') == foldername) {
1142 el.set('selected', true);
1145 selectnode.all('.fp-author input, .fp-license select').set('disabled',(node.type == 'folder')?'disabled':'');
1146 // display static information about a file (when known)
1147 var attrs = ['datemodified','datecreated','size','dimensions','original','reflist'];
1148 for (var i in attrs) {
1149 if (selectnode.one('.fp-'+attrs[i])) {
1150 var value = (node[attrs[i]+'_f']) ? node[attrs[i]+'_f'] : (node[attrs[i]] ? node[attrs[i]] : '');
1151 // Escape if the attribute being evaluated is not for the list of reference files.
1152 if (attrs[i] !== 'reflist') {
1153 value = Y.Escape.html(value);
1155 selectnode.one('.fp-'+attrs[i]).addClassIf('fp-unknown', ''+value == '')
1156 .one('.fp-value').setContent(value);
1159 // display thumbnail
1160 var imgnode = Y.Node.create('<img/>').
1161 set('src', node.realthumbnail ? node.realthumbnail : node.thumbnail).
1162 setStyle('maxHeight', ''+(node.thumbnail_height ? node.thumbnail_height : 90)+'px').
1163 setStyle('maxWidth', ''+(node.thumbnail_width ? node.thumbnail_width : 90)+'px');
1164 selectnode.one('.fp-thumbnail').setContent('').appendChild(imgnode);
1165 // load original location if applicable
1166 if (node.isref && !node.original) {
1167 selectnode.one('.fp-original').removeClass('fp-unknown').addClass('fp-loading');
1169 action: 'getoriginal',
1171 params: {'filepath':node.filepath,'filename':node.fullname},
1172 callback: function(id, obj, args) {
1173 // check if we did not select another file meanwhile
1174 var scope = args.scope;
1175 if (scope.selectui.fileinfo && node &&
1176 scope.selectui.fileinfo.filepath == node.filepath &&
1177 scope.selectui.fileinfo.fullname == node.fullname) {
1178 selectnode.one('.fp-original').removeClass('fp-loading');
1180 node.original = obj.original;
1181 selectnode.one('.fp-original .fp-value').setContent(Y.Escape.html(node.original));
1183 selectnode.one('.fp-original .fp-value').setContent(M.util.get_string('unknownsource', 'repository'));
1189 // load references list if applicable
1190 selectnode.one('.fp-refcount').setContent(node.refcount ? M.util.get_string('referencesexist', 'repository', node.refcount) : '');
1191 if (node.refcount && !node.reflist) {
1192 selectnode.one('.fp-reflist').removeClass('fp-unknown').addClass('fp-loading');
1194 action: 'getreferences',
1196 params: {'filepath':node.filepath,'filename':node.fullname},
1197 callback: function(id, obj, args) {
1198 // check if we did not select another file meanwhile
1199 var scope = args.scope;
1200 if (scope.selectui.fileinfo && node &&
1201 scope.selectui.fileinfo.filepath == node.filepath &&
1202 scope.selectui.fileinfo.fullname == node.fullname) {
1203 selectnode.one('.fp-reflist').removeClass('fp-loading');
1204 if (obj.references) {
1206 for (var i in obj.references) {
1207 node.reflist += '<li>'+Y.Escape.html(obj.references[i])+'</li>';
1209 selectnode.one('.fp-reflist .fp-value').setContent(node.reflist);
1211 selectnode.one('.fp-reflist .fp-value').setContent('');
1217 // update dialog header
1218 var nodename = node.fullname;
1219 // Limit the string length so it fits nicely on mobile devices
1220 var namelength = 50;
1221 if (nodename.length > namelength) {
1222 nodename = nodename.substring(0, namelength) + '...';
1224 Y.one('#fm-dialog-label_'+selectnode.get('id')).setContent(Y.Escape.html(M.util.get_string('edit', 'moodle')+' '+nodename));
1226 this.selectui.show();
1227 Y.one('#'+selectnode.get('id')).focus();
1229 render: function(obj, action) {
1231 this.view_files(null, action);
1233 has_folder: function(foldername) {
1235 for (var i in this.options.list) {
1236 element = this.options.list[i];
1237 if (element.type == 'folder' && element.fullname == foldername) {
1243 get_preference: function(name) {
1244 if (this.userprefs[name]) {
1245 return this.userprefs[name];
1250 set_preference: function(name, value) {
1251 if (this.userprefs[name] != value) {
1252 M.util.set_user_preference('filemanager_' + name, value);
1253 this.userprefs[name] = value;
1258 // finally init everything needed
1259 // hide loading picture, display filemanager interface
1260 var filemanager = Y.one('#filemanager-'+options.client_id);
1261 filemanager.removeClass('fm-loading').addClass('fm-loaded');
1263 var manager = new FileManagerHelper(options);
1265 filemanager: manager,
1266 acceptedtypes: options.filepicker.accepted_types,
1267 clientid: options.client_id,
1268 author: options.author,
1269 maxfiles: options.maxfiles,
1270 maxbytes: options.maxbytes,
1271 areamaxbytes: options.areamaxbytes,
1272 itemid: options.itemid,
1273 repositories: manager.filepicker_options.repositories,
1274 containerid: manager.dndcontainer.get('id'),
1275 contextid: options.context.id
1277 M.form_dndupload.init(Y, dndoptions);