diff --git a/plugins/kolab_files/kolab_files.js b/plugins/kolab_files/kolab_files.js index e2495659..6e7ca0a9 100644 --- a/plugins/kolab_files/kolab_files.js +++ b/plugins/kolab_files/kolab_files.js @@ -1,2904 +1,3032 @@ /** * Kolab files plugin * * @author Aleksander Machniak <machniak@kolabsys.com> * * @licstart The following is the entire license notice for the * JavaScript code in this file. * - * Copyright (C) 2011, Kolab Systems AG <contact@kolabsys.com> + * Copyright (C) 2011-2015, Kolab Systems AG <contact@kolabsys.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * @licend The above is the entire license notice * for the JavaScript code in this file. */ window.rcmail && window.files_api && rcmail.addEventListener('init', function() { if (rcmail.task == 'mail') { // mail compose if (rcmail.env.action == 'compose') { kolab_files_from_cloud_widget($('#compose-attachments > div')); // register some commands to skip warning message on compose page $.merge(rcmail.env.compose_commands, ['files-list', 'files-sort', 'files-search', 'files-search-reset']); } // mail preview else if (rcmail.env.action == 'show' || rcmail.env.action == 'preview') { var attachment_list = $('#attachment-list'); if ($('li', attachment_list).length) { var link = $('<a href="#" class="button filesaveall">') .text(rcmail.gettext('kolab_files.saveall')) .click(function() { kolab_directory_selector_dialog(); }) .insertAfter(attachment_list); } rcmail.addEventListener('menu-open', kolab_files_attach_menu_open); rcmail.enable_command('folder-create', true); } // attachment preview else if (rcmail.env.action == 'get') { rcmail.enable_command('folder-create', true); } if (!rcmail.env.action || rcmail.env.action == 'show' || rcmail.env.action == 'preview') { // add "attach from cloud" button for event/task dialog in mail rcmail.addEventListener('plugin.mail2event_dialog', function() { if (!$('#calendar-attachment-form input.fromcloud').length) kolab_files_from_cloud_widget($('#calendar-attachment-form > div.buttons')); }); } kolab_files_init(); } else if (rcmail.task == 'calendar') { // add "attach from cloud" button for event dialog if (!rcmail.env.action) kolab_files_from_cloud_widget($('#calendar-attachment-form > div.buttons')); kolab_files_init(); } else if (rcmail.task == 'tasks') { // add "attach from cloud" button for task dialog if (!rcmail.env.action) kolab_files_from_cloud_widget($('#taskedit-attachment-form > div.buttons')); kolab_files_init(); } else if (rcmail.task == 'files') { if (rcmail.gui_objects.filelist) { rcmail.file_list = new rcube_list_widget(rcmail.gui_objects.filelist, { multiselect: true, draggable: true, keyboard: true, column_movable: rcmail.env.col_movable, dblclick_time: rcmail.dblclick_time }); rcmail.file_list.addEventListener('dblclick', function(o) { kolab_files_list_dblclick(o); }) .addEventListener('select', function(o) { kolab_files_list_select(o); }) .addEventListener('keypress', function(o) { kolab_files_list_keypress(o); }) .addEventListener('dragstart', function(e) { kolab_files_drag_start(e); }) .addEventListener('dragmove', function(e) { kolab_files_drag_move(e); }) .addEventListener('dragend', function(e) { kolab_files_drag_end(e); }) .addEventListener('column_replace', function(e) { kolab_files_set_coltypes(e); }) .addEventListener('listupdate', function(e) { rcmail.triggerEvent('listupdate', e); }); rcmail.enable_command('menu-open', 'menu-save', 'files-sort', 'files-search', 'files-search-reset', 'folder-create', true); rcmail.file_list.init(); kolab_files_list_coltypes(); kolab_files_drag_drop_init($(rcmail.gui_objects.filelist).parents('.droptarget')); } // "one file only" commands rcmail.env.file_commands = ['files-get', 'files-rename']; // "one or more file" commands rcmail.env.file_commands_all = ['files-delete', 'files-move', 'files-copy']; kolab_files_init(); if (rcmail.env.action == 'open' || rcmail.env.action == 'edit') { rcmail.enable_command('files-get', 'files-delete', rcmail.env.file); if (rcmail.env.action == 'edit' && rcmail.env.file_data.viewer && rcmail.env.file_data.viewer.manticore) manticore = new manticore_api({ iframe: $('#fileframe').get(0), export_menu: rcmail.gui_objects.exportmenu ? $('ul', rcmail.gui_objects.exportmenu).get(0) : null, title_input: $('#document-title').get(0), members_list: $('#members').get(0), photo_url: '?_task=addressbook&_action=photo&_email=%email', photo_default_url: rcmail.env.photo_placeholder, ready: function(data) { manticore_init(); }, set_busy: function(state, message) { return rcmail.set_busy(state, message ? 'kolab_files.' + message : ''); }, hide_message: function(id) { return rcmail.hide_message(id); }, display_message: function(label, type) { return rcmail.display_message('kolab_files.' + label, type); }, gettext: function(label) { return rcmail.get_label('kolab_files.' + label); } }); + + if (rcmail.env.action == 'open') { + // initialize folders list (for dialogs) + file_api.folder_list(); + + // get ongoing sessions + file_api.request('folder_info', {folder: file_api.file_path(rcmail.env.file), sessions: 1}, 'folder_info_response'); + } } else { file_api.folder_list(); file_api.browser_capabilities_check(); rcmail.enable_command('folder-mount', rcmail.env.external_sources); } } }); /**********************************************************/ /********* Shared functionality **********/ /**********************************************************/ // Initializes API object function kolab_files_init() { if (window.file_api) return; // Initialize application object (don't change var name!) file_api = $.extend(new files_api(), new kolab_files_ui()); file_api.set_env({ token: kolab_files_token(), url: rcmail.env.files_url, sort_col: 'name', sort_reverse: false, search_threads: rcmail.env.search_threads, resources_dir: rcmail.assets_path('program/resources'), caps: rcmail.env.files_caps, supported_mimetypes: rcmail.env.file_mimetypes }); file_api.translations = rcmail.labels; }; // returns API authorization token function kolab_files_token() { // consider the token from parent window more reliable (fresher) than in framed window // it's because keep-alive is not requested in frames return window.parent && parent.rcmail && parent.rcmail.env.files_token ? parent.rcmail.env.files_token : rcmail.env.files_token; }; function kolab_files_from_cloud_widget(elem) { var input = $('<input class="button fromcloud" type="button">') .attr('tabindex', $('input', elem).attr('tabindex') || 0) .val(rcmail.gettext('kolab_files.fromcloud')) .click(function() { kolab_files_selector_dialog(); }) .appendTo(elem); if (rcmail.gui_objects.filelist) { rcmail.file_list = new rcube_list_widget(rcmail.gui_objects.filelist, { multiselect: true, keyboard: true, column_movable: false, dblclick_time: rcmail.dblclick_time }); rcmail.file_list.addEventListener('select', function(o) { kolab_files_list_select(o); }) .addEventListener('listupdate', function(e) { rcmail.triggerEvent('listupdate', e); }); rcmail.enable_command('files-sort', 'files-search', 'files-search-reset', true); rcmail.file_list.init(); kolab_files_list_coltypes(); } } // folder selection dialog function kolab_directory_selector_dialog(id) { var dialog = $('#files-dialog'), input = $('#file-save-as-input'), form = $('#file-save-as'), list = $('#folderlistbox'), buttons = {}, label = 'saveto', win = window, fn; // attachment is specified if (id) { var attach = $('#attach' + id + '> a').first(), filename = attach.attr('title'); if (!filename) { attach = attach.clone(); $('.attachment-size', attach).remove(); filename = $.trim(attach.text()); } form.show(); dialog.addClass('saveas'); input.val(filename); } // attachment preview page else if (rcmail.env.action == 'get') { id = rcmail.env.part; form.show(); dialog.addClass('saveas'); input.val(rcmail.env.filename); } else { form.hide(); dialog.removeClass('saveas'); label = 'saveall'; } $('#foldercreatelink').attr('tabindex', 0); buttons[rcmail.gettext('kolab_files.save')] = function () { if (!file_api.env.folder) return; var lock = rcmail.set_busy(true, 'saving'), request = { act: 'save-file', source: rcmail.env.mailbox, uid: rcmail.env.uid, dest: file_api.env.folder }; if (id) { request.id = id; request.name = input.val(); } rcmail.http_post('plugin.kolab_files', request, lock); kolab_dialog_close(this); }; buttons[rcmail.gettext('kolab_files.cancel')] = function () { kolab_dialog_close(this); }; if (!rcmail.env.folders_loaded) { fn = function() { rcmail.env.folder_list_selector = '#files-dialog #files-folder-list'; rcmail.env.folder_search_selector = '#files-dialog #foldersearch'; file_api.folder_list({writable: 1}); rcmail.env.folders_loaded = true; }; } // show dialog window kolab_dialog_show(dialog, { title: rcmail.gettext('kolab_files.' + label), buttons: buttons, button_classes: ['mainaction'], minWidth: 250, minHeight: 300, height: 400, width: 300 }, fn); // "enable" folder creation when dialog is displayed in parent window if (rcmail.is_framed()) { parent.rcmail.enable_command('folder-create', true); parent.rcmail.folder_create = function() { win.kolab_files_folder_create_dialog(); }; } }; // file selection dialog function kolab_files_selector_dialog() { var dialog = $('#files-compose-dialog'), buttons = {}; buttons[rcmail.gettext('kolab_files.attachsel')] = function () { var list = []; $('#filelist tr.selected').each(function() { list.push($(this).data('file')); }); kolab_dialog_close(this); if (list.length) { // display upload indicator and cancel button var content = '<span>' + rcmail.get_label('kolab_files.attaching') + '</span>', id = new Date().getTime(); rcmail.add2attachment_list(id, {name:'', html:content, classname:'uploading', complete:false}); // send request rcmail.http_post('plugin.kolab_files', { act: 'attach-file', files: list, id: rcmail.env.compose_id, uploadid: id }); } }; buttons[rcmail.gettext('kolab_files.cancel')] = function () { kolab_dialog_close(this); }; // show dialog window kolab_dialog_show(dialog, { title: rcmail.gettext('kolab_files.selectfiles'), buttons: buttons, button_classes: ['mainaction'], minWidth: 500, minHeight: 300, width: 700, height: 500 }, function() { rcmail.file_list.resize(); }); if (!rcmail.env.files_loaded) { rcmail.env.folder_list_selector = '#files-compose-dialog #files-folder-list'; rcmail.env.folder_search_selector = '#files-compose-dialog #foldersearch'; file_api.folder_list(); rcmail.env.files_loaded = true; } else { rcmail.file_list.clear_selection(); } }; function kolab_files_attach_menu_open(p) { if (!p || !p.props || p.props.menu != 'attachmentmenu') return; var id = p.props.id; $('#attachmenusaveas').unbind('click').attr('onclick', '').click(function(e) { return kolab_directory_selector_dialog(id); }); }; // folder creation dialog function kolab_files_folder_create_dialog() { var dialog = $('#files-folder-create-dialog'), buttons = {}, select = $('select[name="parent"]', dialog).html(''), input = $('input[name="name"]', dialog).val(''); buttons[rcmail.gettext('kolab_files.create')] = function () { var folder = '', name = input.val(), parent = select.val(); if (!name) return; if (parent) folder = parent + file_api.env.directory_separator; folder += name; file_api.folder_create(folder); kolab_dialog_close(this); }; buttons[rcmail.gettext('kolab_files.cancel')] = function () { kolab_dialog_close(this); }; // show dialog window kolab_dialog_show(dialog, { title: rcmail.gettext('kolab_files.foldercreate'), buttons: buttons, button_classes: ['mainaction'] }); // Fix submitting form with Enter $('form', dialog).submit(kolab_dialog_submit_handler); // build parent selector - kolab_files_folder_select_element(select, {empty: true}); + file_api.folder_select_element(select, {empty: true, writable: true}); }; // folder edit dialog function kolab_files_folder_edit_dialog() { var dialog = $('#files-folder-edit-dialog'), buttons = {}, separator = file_api.env.directory_separator, arr = file_api.env.folder.split(separator), folder = arr.pop(), path = arr.join(separator), select = $('select[name="parent"]', dialog).html(''), input = $('input[name="name"]', dialog).val(folder); buttons[rcmail.gettext('kolab_files.save')] = function () { var folder = '', name = input.val(), parent = select.val(); if (!name) return; if (parent) folder = parent + separator; folder += name; file_api.folder_rename(file_api.env.folder, folder); kolab_dialog_close(this); }; buttons[rcmail.gettext('kolab_files.cancel')] = function () { kolab_dialog_close(this); }; // show dialog window kolab_dialog_show(dialog, { title: rcmail.gettext('kolab_files.folderedit'), buttons: buttons, button_classes: ['mainaction'] }); // Fix submitting form with Enter $('form', dialog).submit(kolab_dialog_submit_handler); // build parent selector - kolab_files_folder_select_element(select, {selected: path, empty: true}); + file_api.folder_select_element(select, {selected: path, empty: true}); }; // folder mounting dialog function kolab_files_folder_mount_dialog() { var args = {buttons: {}, title: rcmail.gettext('kolab_files.foldermount')}, dialog = $('#files-folder-mount-dialog'), input = $('#folder-mount-name').val(''); args.buttons[rcmail.gettext('kolab_files.save')] = function () { var args = {}, folder = input.val(), driver = $('input[name="driver"]:checked', dialog).val(); if (!folder || !driver) return; args.folder = folder; args.driver = driver; $('#source-' + driver + ' input').each(function() { if (this.name.startsWith(driver + '[')) { args[this.name.substring(driver.length + 1, this.name.length - 1)] = this.value; } }); $('.auth-options input', dialog).each(function() { args[this.name] = this.type == 'checkbox' && !this.checked ? '' : this.value; }); file_api.folder_mount(args); kolab_dialog_close(this); }; args.buttons[rcmail.gettext('kolab_files.cancel')] = function () { kolab_dialog_close(this); }; // close folderoption menu rcmail.hide_menu('folderoptions'); // initialize drivers list if (!rcmail.drivers_list_initialized) { rcmail.drivers_list_initialized = true; $('td.source', dialog).each(function() { $(this).click(function() { $('td.selected', dialog).removeClass('selected'); dialog.find('.driverform').hide(); $(this).addClass('selected').find('.driverform').show(); $('input[type="radio"]', this).prop('checked', true); }); }); } args.button_classes = ['mainaction']; // show dialog window kolab_dialog_show(dialog, args, function() { $('td.source:first', dialog).click(); input.focus(); }); }; +// file edit dialog +function kolab_files_file_edit_dialog(file, sessions, readonly) +{ + var content = [], items = [], height = 300; + dialog = $('#files-file-edit-dialog'), + buttons = {}, name = file_api.file_name(file), + title = rcmail.gettext('kolab_files.editfiledialog'), + mainaction = rcmail.gettext('kolab_files.select'), + item_fn = function(id, txt, classes) { + return $('<label>').attr('class', 'session' + (classes ? ' ' + classes : '')) + .append($('<input>').attr({name: 'opt', value: id, type: 'radio'})).append($('<span>').text(txt)); + }, + select_fn = function(dlg) { + var input = $('input:checked', dialog), id = input.val(); + + if (dlg) + kolab_dialog_close(dlg); + + if (readonly && (id == 0 || !input.length)) + return kolab_files_file_create_dialog(file); + + // @todo: invitations + rcmail.files_edit(id ? id : true); + }; + + // Create sessions selection + if (sessions && sessions.length) { + items.push($('<div>').text(rcmail.gettext('kolab_files.editfilesessions'))); + + // first display owned sessions, then invited, other at the end + $.each(sessions, function() { + if (this.is_owner) { + var txt = rcmail.gettext('kolab_files.ownedsession'); + items.push(item_fn(this.id, txt, 'owner')); + } + }); + + if (items.length == 1) + items.push(item_fn(0, rcmail.gettext('kolab_files.newsession' + (readonly ? 'ro' : '')))); + + $.each(sessions, function() { + if (this.is_invited) { + var txt = rcmail.gettext('kolab_files.invitedsession').replace('$user', this.owner); + items.push(item_fn(this.id, txt, 'invited')); + } + }); + + $.each(sessions, function() { + if (!this.is_owner && !this.is_invited) { + var txt = rcmail.gettext('kolab_files.joinsession').replace('$user', this.owner); + items.push(item_fn(this.id, txt)); + } + }); + + // check the first option + $('input', items[1]).attr('checked', true); + + $('div', dialog).html(items); + + // if there's only one session and it's owned, skip the dialog + if (!readonly && items.length == 2 && $('input:checked', dialog).parent().is('.owner')) + return select_fn(); + } + // no ongoing session, folder is readonly warning + else { + title = rcmail.gettext('kolab_files.editfilerotitle'); + height = 150; + $('div', dialog).text(rcmail.gettext('kolab_files.editfilero')); + mainaction = rcmail.gettext('kolab_files.create'); + } + + buttons[mainaction] = function() { select_fn(this); }; + buttons[rcmail.gettext('kolab_files.cancel')] = function () { + kolab_dialog_close(this); + }; + + // show dialog window + kolab_dialog_show(dialog, { + title: title, + buttons: buttons, + button_classes: ['mainaction'], + minHeight: height - 100, + height: height + }); +}; + // file rename dialog function kolab_files_file_rename_dialog(file) { var dialog = $('#files-file-rename-dialog'), buttons = {}, name = file_api.file_name(file) input = $('input[name="name"]', dialog).val(name); - buttons[rcmail.gettext('kolab_files.save')] = function () { + buttons[rcmail.gettext('kolab_files.save')] = function() { var folder = file_api.file_path(file), name = input.val(); if (!name) return; name = folder + file_api.env.directory_separator + name; if (name != file) file_api.file_rename(file, name); kolab_dialog_close(this); }; - buttons[rcmail.gettext('kolab_files.cancel')] = function () { + buttons[rcmail.gettext('kolab_files.cancel')] = function() { kolab_dialog_close(this); }; // Fix submitting form with Enter $('form', dialog).submit(kolab_dialog_submit_handler); // show dialog window kolab_dialog_show(dialog, { title: rcmail.gettext('kolab_files.renamefile'), buttons: buttons, button_classes: ['mainaction'], minHeight: 100, height: 200 }); }; -// file creation dialog -function kolab_files_file_create_dialog() +// file creation (or cloning) dialog +function kolab_files_file_create_dialog(file) { - var dialog = $('#files-file-create-dialog'), - buttons = {}, + var buttons = {}, action = file ? 'copy' : 'create', + dialog = $('#files-file-create-dialog'), type_select = $('select[name="type"]', dialog), select = $('select[name="parent"]', dialog).html(''), input = $('input[name="name"]', dialog).val(''), - create_func = function (dialog, editaction) { - var folder = select.val(), type = type_select.val(), name = input.val(); + create_func = function(dialog, editaction) { + var sel, folder = select.val(), type = type_select.val(), name = input.val(); if (!name || !folder) return; if (!/\.[a-z0-9]{1,5}$/.test(name)) { name += '.' + rcmail.env.file_extensions[type]; } name = folder + file_api.env.directory_separator + name; - file_api.file_create(name, type, editaction); + // get type of cloned file + if (file) { + if (rcmail.env.file_data) + type = rcmail.env.file_data.type; + else { + sel = rcmail.file_list.get_selection(); + type = $('#rcmrow' + sel[0]).data('type'); + } + } + + file_api.file_create(name, type, editaction, file); kolab_dialog_close(dialog); }; - buttons[rcmail.gettext('kolab_files.createandedit')] = function () { + buttons[rcmail.gettext('kolab_files.' + action + 'andedit')] = function() { create_func(this, true); }; - buttons[rcmail.gettext('kolab_files.create')] = function () { - create_func(this); - }; - buttons[rcmail.gettext('kolab_files.cancel')] = function () { + + if (action == 'create') { + buttons[rcmail.gettext('kolab_files.create')] = function() { + create_func(this); + }; + type_select.parent('tr').show(); + } + else { + input.val(file_api.file_name(file)); + type_select.parent('tr').hide(); + } + + buttons[rcmail.gettext('kolab_files.cancel')] = function() { kolab_dialog_close(this); }; // Fix submitting form with Enter $('form', dialog).submit(kolab_dialog_submit_handler); // show dialog window kolab_dialog_show(dialog, { - title: rcmail.gettext('kolab_files.createfile'), + title: rcmail.gettext('kolab_files.' + action + 'file'), buttons: buttons, - button_classes: ['mainaction'] + button_classes: ['mainaction'], + minHeight: 150, + height: 250 }); // build folder selector - kolab_files_folder_select_element(select); -}; - -// builds folder selector options -function kolab_files_folder_select_element(select, params) -{ - var options = [], - selected = params && params.selected ? params.selected : file_api.env.folder; - - if (params && params.empty) - options.push($('<option>').val('').text('---')); - - $.each(file_api.env.folders, function(i, f) { - var n, name = escapeHTML(f.name); - - for (n=0; n<f.depth; n++) - name = ' ' + name; - - options.push($('<option>').val(i).html(name)); - }); - - select.empty().append(options); - - if (selected) - select.val(selected); + file_api.folder_select_element(select, {writable: true}); }; function kolab_dialog_show(content, params, onopen) { params = $.extend({ modal: true, resizable: true, closeOnEscape: (!bw.ie6 && !bw.ie7), // disabled for performance reasons minWidth: 400, minHeight: 300, width: 500, height: 400 }, params || {}); // dialog close handler params.close = function(e, ui) { var elem, stack = rcmail.dialog_stack; content.appendTo(document.body).hide(); $(this).parent().remove(); // remove dialog // focus previously focused element (guessed) stack.pop(); if (stack.length) { elem = stack[stack.length-1].find('input[type!="hidden"]:not(:hidden):first'); if (!elem.length) elem = stack[stack.length-1].parent().find('a[role="button"], .ui-dialog-buttonpane button').first(); } (elem && elem.length ? elem : window).focus(); }; // display it as popup var dialog = rcmail.show_popup_dialog('', params.title, params.buttons, params); content.appendTo(dialog).show().find('input[type!="hidden"]:not(:hidden):first').focus(); if (onopen) onopen(content); // save dialog reference, to handle focus when closing one of opened dialogs if (!rcmail.dialog_stack) rcmail.dialog_stack = []; rcmail.dialog_stack.push(dialog); }; // Handle form submit with Enter key, click first dialog button instead function kolab_dialog_submit_handler() { $(this).parents('.ui-dialog').find('.ui-button').first().click(); return false; }; // Hides dialog function kolab_dialog_close(dialog) { (rcmail.is_framed() ? window.parent : window).$(dialog).dialog('close'); }; // smart upload button function kolab_files_upload_input(button) { var link = $(button), file = $('<input>'), offset = link.offset(); function move_file_input(e) { file.css({top: (e.pageY - offset.top - 10) + 'px', left: (e.pageX - offset.left - 10) + 'px'}); } file.attr({name: 'file[]', type: 'file', multiple: 'multiple', size: 5, title: link.attr('title'), tabindex: "-1"}) .change(function() { rcmail.files_upload('#filesuploadform'); }) .click(function() { setTimeout(function() { link.mouseleave(); }, 20); }) // opacity:0 does the trick, display/visibility doesn't work .css({opacity: 0, cursor: 'pointer', outline: 'none', position: 'absolute'}); // In FF and IE we need to move the browser file-input's button under the cursor // Thanks to the size attribute above we know the length of the input field if (bw.mz || bw.ie) file.css({marginLeft: '-80px'}); // Note: now, I observe problem with cursor style on FF < 4 only // Need position: relative (Bug #2615) link.css({overflow: 'hidden', cursor: 'pointer', position: 'relative'}) .mouseenter(function() { this.__active = true; }) // place button under the cursor .mousemove(function(e) { if (rcmail.commands['files-upload'] && this.__active) move_file_input(e); // move the input away if button is disabled else $(this).mouseleave(); }) .mouseleave(function() { file.css({top: '-10000px', left: '-10000px'}); this.__active = false; }) .attr('onclick', '') // remove default button action .click(function(e) { // forward click if mouse-enter event was missed if (rcmail.commands['files-upload'] && !this.__active) { this.__active = true; move_file_input(e); file.trigger(e); } }) .mouseleave() // initially disable/hide input .append(file); }; /***********************************************************/ /********** Main functionality **********/ /***********************************************************/ // for reordering column array (Konqueror workaround) // and for setting some message list global variables function kolab_files_list_coltypes() { var n, list = rcmail.file_list; rcmail.env.subject_col = null; if ((n = $.inArray('name', rcmail.env.file_coltypes)) >= 0) { rcmail.env.subject_col = n; list.subject_col = n; } list.init_header(); }; function kolab_files_set_list_options(cols, sort_col, sort_order) { var update = 0, i, idx, name, newcols = [], oldcols = rcmail.env.file_coltypes; if (sort_col === undefined) sort_col = rcmail.env.sort_col; if (!sort_order) sort_order = rcmail.env.sort_order; if (rcmail.env.sort_col != sort_col || rcmail.env.sort_order != sort_order) { update = 1; rcmail.set_list_sorting(sort_col, sort_order); } if (cols && cols.length) { // make sure new columns are added at the end of the list for (i=0; i<oldcols.length; i++) { name = oldcols[i]; idx = $.inArray(name, cols); if (idx != -1) { newcols.push(name); delete cols[idx]; } } for (i=0; i<cols.length; i++) if (cols[i]) newcols.push(cols[i]); if (newcols.join() != oldcols.join()) { update += 2; oldcols = newcols; } } if (update == 1) rcmail.command('files-list', {sort: sort_col, reverse: sort_order == 'DESC'}); else if (update) { rcmail.http_post('files/prefs', { kolab_files_list_cols: oldcols, kolab_files_sort_col: sort_col, kolab_files_sort_order: sort_order }, rcmail.set_busy(true, 'loading')); } }; function kolab_files_set_coltypes(list) { var i, found, name, cols = list.list.tHead.rows[0].cells; rcmail.env.file_coltypes = []; for (i=0; i<cols.length; i++) if (cols[i].id && cols[i].id.match(/^rcm/)) { name = cols[i].id.replace(/^rcm/, ''); rcmail.env.file_coltypes.push(name); } // if ((found = $.inArray('name', rcmail.env.file_coltypes)) >= 0) // rcmail.env.subject_col = found; rcmail.env.subject_col = list.subject_col; rcmail.http_post('files/prefs', {kolab_files_list_cols: rcmail.env.file_coltypes}); }; function kolab_files_list_dblclick(list) { rcmail.command('files-open'); }; function kolab_files_list_select(list) { var selected = list.selection.length; rcmail.enable_command(rcmail.env.file_commands_all, selected); rcmail.enable_command(rcmail.env.file_commands, selected == 1); // reset all-pages-selection // if (list.selection.length && list.selection.length != list.rowcount) // rcmail.select_all_mode = false; if (selected == 1) { // get file mimetype var elem = $('tr.selected', list.list), type = elem.data('type'), - file = elem.data('file'); + folder = file_api.file_path(elem.data('file')); rcmail.env.viewer = file_api.file_type_supported(type, rcmail.env.files_caps); - if (!file_api.is_writable(file.replace(/\/[^/]+$/, ''))) + if (!file_api.is_writable(folder)) rcmail.enable_command('files-delete', 'files-rename', false); } else rcmail.env.viewer = 0; rcmail.enable_command('files-edit', (rcmail.env.viewer & 4) == 4); rcmail.enable_command('files-open', rcmail.env.viewer); }; function kolab_files_list_keypress(list) { if (list.modkey == CONTROL_KEY) return; if (list.key_pressed == list.ENTER_KEY) rcmail.command('files-open'); else if (list.key_pressed == list.DELETE_KEY || list.key_pressed == list.BACKSPACE_KEY) rcmail.command('files-delete'); }; function kolab_files_drag_start(e) { rcmail.env.drag_target = null; if (rcmail.folder_list) rcmail.folder_list.drag_start(); }; function kolab_files_drag_end(e) { if (rcmail.folder_list) { rcmail.folder_list.drag_end(); if (rcmail.env.drag_target) { var modkey = rcube_event.get_modifier(e), menu = rcmail.gui_objects.file_dragmenu; rcmail.file_list.draglayer.hide(); if (menu && modkey == SHIFT_KEY && rcmail.commands['files-copy']) { var pos = rcube_event.get_mouse_pos(e); $(menu).css({top: (pos.y-10)+'px', left: (pos.x-10)+'px'}).show(); return; } rcmail.command('files-move', rcmail.env.drag_target); } } }; function kolab_files_drag_move(e) { if (rcmail.folder_list) { var mouse = rcube_event.get_mouse_pos(e); rcmail.env.drag_target = rcmail.folder_list.intersects(mouse, true); } }; function kolab_files_drag_menu_action(command) { var menu = rcmail.gui_objects.file_dragmenu; if (menu) $(menu).hide(); rcmail.command(command, rcmail.env.drag_target); }; function kolab_files_selected() { var files = []; $.each(rcmail.file_list.get_selection(), function(i, v) { var name, row = $('#rcmrow'+v); if (row.length == 1 && (name = row.data('file'))) files.push(name); }); return files; }; function kolab_files_frame_load(frame) { var win = frame.contentWindow; try { rcmail.file_editor = win.file_editor && win.file_editor.editable ? win.file_editor : null; } catch (e) {}; // on edit page switch immediately to edit mode if (rcmail.file_editor && rcmail.env.action == 'edit') rcmail.files_edit(); rcmail.enable_command('files-edit', rcmail.file_editor || (rcmail.env.file_data.viewer && rcmail.env.file_data.viewer.manticore)); rcmail.enable_command('files-print', (rcmail.file_editor && rcmail.file_editor.printable) || (rcmail.env.file_data && /^image\//i.test(rcmail.env.file_data.type))); // detect Print button and check if it can be accessed try { if ($('#fileframe').contents().find('#print').length) rcmail.enable_command('files-print', true); } catch(e) {}; }; // activate html5 file drop feature (if browser supports it) function kolab_files_drag_drop_init(container) { if (!window.FormData && !(window.XMLHttpRequest && XMLHttpRequest.prototype && XMLHttpRequest.prototype.sendAsBinary)) { return; } if (!container.length) return; $(document.body).bind('dragover dragleave drop', function(e) { if (!file_api.is_writable()) return; e.preventDefault(); container[e.type == 'dragover' ? 'addClass' : 'removeClass']('active'); }); container.bind('dragover dragleave', function(e) { return kolab_files_drag_hover(e); }) container.children('div').bind('dragover dragleave', function(e) { return kolab_files_drag_hover(e); }) container.get(0).addEventListener('drop', function(e) { // abort event and reset UI kolab_files_drag_hover(e); return file_api.file_drop(e); }, false); }; // handler for drag/drop on element function kolab_files_drag_hover(e) { if (!file_api.is_writable()) return; e.preventDefault(); e.stopPropagation(); var elem = $(e.target); if (!elem.hasClass('droptarget')) elem = elem.parents('.droptarget'); elem[e.type == 'dragover' ? 'addClass' : 'removeClass']('hover'); }; // returns localized file size function kolab_files_file_size(size) { var i, units = ['GB', 'MB', 'KB', 'B']; size = file_api.file_size(size); for (i = 0; i < units.length; i++) if (size.toUpperCase().indexOf(units[i]) > 0) return size.replace(units[i], rcmail.gettext(units[i])); return size; }; function kolab_files_progress_str(param) { var current, total = file_api.file_size(param.total).toUpperCase(); if (total.indexOf('GB') > 0) current = parseFloat(param.current/1073741824).toFixed(1); else if (total.indexOf('MB') > 0) current = parseFloat(param.current/1048576).toFixed(1); else if (total.indexOf('KB') > 0) current = parseInt(param.current/1024); else current = param.current; total = kolab_files_file_size(param.total); return rcmail.gettext('uploadprogress') .replace(/\$percent/, param.percent + '%') .replace(/\$current/, current) .replace(/\$total/, total); }; /**********************************************************/ /********* Manticore editor functionality **********/ /**********************************************************/ // Initialize document toolbar functionality function manticore_init() { rcmail.enable_command('document-save', 'document-export', true); }; rcube_webmail.prototype.document_save = function() { manticore.save(); }; rcube_webmail.prototype.document_export = function(type) { manticore.export(type || 'odt'); }; /***********************************************************/ /********** Commands **********/ /***********************************************************/ rcube_webmail.prototype.files_sort = function(props) { var params = {}, sort_order = this.env.sort_order, sort_col = !this.env.disabled_sort_col ? props : this.env.sort_col; if (!this.env.disabled_sort_order) sort_order = this.env.sort_col == sort_col && sort_order == 'ASC' ? 'DESC' : 'ASC'; // set table header and update env this.set_list_sorting(sort_col, sort_order); this.http_post('files/prefs', {kolab_files_sort_col: sort_col, kolab_files_sort_order: sort_order}); params.sort = sort_col; params.reverse = sort_order == 'DESC'; this.command('files-list', params); }; rcube_webmail.prototype.files_search = function() { var value = $(this.gui_objects.filesearchbox).val(); if (value) file_api.file_search(value, $('#search_all_folders').is(':checked')); else file_api.file_search_reset(); }; rcube_webmail.prototype.files_search_reset = function() { $(this.gui_objects.filesearchbox).val(''); file_api.file_search_reset(); }; rcube_webmail.prototype.files_folder_delete = function() { if (confirm(this.get_label('kolab_files.folderdeleteconfirm'))) file_api.folder_delete(file_api.env.folder); }; rcube_webmail.prototype.files_delete = function() { if (!confirm(this.get_label('kolab_files.filedeleteconfirm'))) return; var files = this.env.file ? [this.env.file] : kolab_files_selected(); file_api.file_delete(files); }; rcube_webmail.prototype.files_move = function(folder) { var files = kolab_files_selected(); file_api.file_move(files, folder); }; rcube_webmail.prototype.files_copy = function(folder) { var files = kolab_files_selected(); file_api.file_copy(files, folder); }; rcube_webmail.prototype.files_upload = function(form) { if (form) file_api.file_upload(form); }; rcube_webmail.prototype.files_list = function(param) { // just rcmail wrapper, to handle command busy states file_api.file_list(param); } rcube_webmail.prototype.files_list_update = function(head) { var list = this.file_list; list.clear(); $('thead', list.fixed_header ? list.fixed_header : list.list).html(head); kolab_files_list_coltypes(); file_api.file_list(); }; rcube_webmail.prototype.files_get = function() { var files = this.env.file ? [this.env.file] : kolab_files_selected(); if (files.length == 1) file_api.file_get(files[0], {'force-download': true}); }; rcube_webmail.prototype.files_open = function() { var files = kolab_files_selected(); if (files.length == 1) file_api.file_open(files[0], rcmail.env.viewer); }; // enable file editor -rcube_webmail.prototype.files_edit = function() +rcube_webmail.prototype.files_edit = function(session) { - if (this.file_editor) { + var files, readonly, sessions, file = this.env.file, + params = {action: 'edit'}; + + if (!file && !this.env.action) { + files = kolab_files_selected(); + if (files.length == 1) + file = files[0]; + } + + // check if the folder is read-only or there are ongoing sessions + // in such cases display dialog for the user to decide what to do + if (!session) { + sessions = file_api.file_sessions(file); + if (sessions.length || (readonly = !file_api.is_writable(file_api.file_path(file)))) { + kolab_files_file_edit_dialog(file, sessions, readonly); + return; + } + } + else if (session !== true) + params.session = session; + + if (this.file_editor && !session) { this.file_editor.enable(); this.enable_command('files-save', true); } else if (this.env.file) { var viewer = file_api.file_type_supported(this.env.file_data.type, this.env.files_caps); - file_api.file_open(this.env.file, viewer, 'edit', true); + params.local = true; + file_api.file_open(file, viewer, params); } - else if (!this.env.action) { - var files = kolab_files_selected(); - - if (files.length == 1) - file_api.file_open(files[0], this.env.viewer, 'edit'); + else if (file) { + file_api.file_open(file, this.env.viewer, params); } }; rcube_webmail.prototype.files_save = function() { if (!this.file_editor) return; // binary files like ODF need to be updated using FormData if (this.file_editor.getContentCallback) { if (!file_api.file_uploader_support()) return; file_api.req = file_api.set_busy(true, 'saving'); // this.file_editor.disable(); this.file_editor.getContentCallback(function(content, filename) { file_api.file_uploader([content], { action: 'file_update', params: {file: rcmail.env.file, info: 1, token: file_api.env.token}, response_handler: 'file_save_response', fieldname: 'content', single: true }); }); return; } var content = this.file_editor.getContent(); file_api.file_save(this.env.file, content); }; rcube_webmail.prototype.files_print = function() { if (this.file_editor && this.file_editor.printable) this.file_editor.print(); else if (/^image\//i.test(this.env.file_data.type)) { var frame = $('#fileframe').get(0), win = frame ? frame.contentWindow : null; if (win) { win.focus(); win.print(); } } else { // e.g. Print button in PDF viewer try { $('#fileframe').contents().find('#print').click(); } catch(e) {}; } }; rcube_webmail.prototype.files_set_quota = function(p) { if (p.total && window.file_api) { p.used *= 1024; p.total *= 1024; p.title = file_api.file_size(p.used) + ' / ' + file_api.file_size(p.total) + ' (' + p.percent + '%)'; } p.type = this.env.quota_type; this.set_quota(p); }; rcube_webmail.prototype.files_create = function() { kolab_files_file_create_dialog(); }; rcube_webmail.prototype.files_rename = function() { var files = kolab_files_selected(); kolab_files_file_rename_dialog(files[0]); }; rcube_webmail.prototype.folder_create = function() { kolab_files_folder_create_dialog(); }; rcube_webmail.prototype.folder_rename = function() { kolab_files_folder_edit_dialog(); }; rcube_webmail.prototype.folder_mount = function() { kolab_files_folder_mount_dialog(); }; /**********************************************************/ /********* Files API handler **********/ /**********************************************************/ function kolab_files_ui() { this.requests = {}; - this.sessions = {}; this.uploads = []; /* // Called on "session expired" session this.logout = function(response) {}; // called when a request timed out this.request_timed_out = function() {}; // called on start of the request this.set_request_time = function() {}; // called on request response this.update_request_time = function() {}; */ // set state this.set_busy = function(a, message) { if (this.req) rcmail.hide_message(this.req); return rcmail.set_busy(a, message); }; // displays error message this.display_message = function(label, type) { return rcmail.display_message(this.t(label), type); }; this.http_error = function(request, status, err) { rcmail.http_error(request, status, err); }; // check if specified/current folder/view is writable this.is_writable = function(folder) { if (!folder) folder = this.env.folder; if (!folder) return false; var all_folders = $.extend({}, this.env.folders, this.search_results); if (!all_folders[folder] || all_folders[folder].readonly || all_folders[folder].virtual) return false; return true; }; // folders list request this.folder_list = function(params) { if (!params) params = {} params.permissions = 1; this.req = this.set_busy(true, 'loading'); this.request('folder_list', this.list_params = params, 'folder_list_response'); }; // folder list response handler this.folder_list_response = function(response) { if (!this.response(response)) return; var first, body, rows = [], list_selector = rcmail.env.folder_list_selector || '#files-folder-list', search_selector = rcmail.env.folder_search_selector || '#foldersearch', elem = $(list_selector), searchbox = $(search_selector), list = $('<ul class="treelist listing folderlist"></ul>'), collections = ['audio', 'video', 'image', 'document']; // try parent window if the list element does not exist // i.e. called from dialog in parent window if (!elem.length && window.parent && parent.rcmail) { body = window.parent.document.body; elem = $(list_selector, body); searchbox = $(search_selector, body); } if (elem.data('no-collections') == true) collections = []; elem.html(''); this.env.folders = this.folder_list_parse(response.result && response.result.list ? response.result.list : response.result); $.each(this.env.folders, function(i, f) { var row; if (row = file_api.folder_list_row(i, f)) { if (!first) first = i; rows.push(row); } }); // add virtual collections $.each(collections, function(i, n) { var row = $('<li class="mailbox collection ' + n + '"></li>'); row.attr('id', 'rcmli' + rcmail.html_identifier_encode('folder-collection-' + n)) .append($('<a class="name"></a>').text(rcmail.gettext('kolab_files.collection_' + n))) rows.push(row); }); list.append(rows).appendTo(elem) .on('click', 'a.subscription', function(e) { return file_api.folder_list_subscription_button_click(this); }); if (rcmail.folder_list) { rcmail.folder_list.reset(); this.search_results_widget = null; } // init treelist widget rcmail.folder_list = new rcube_treelist_widget(list, { selectable: true, id_prefix: 'rcmli', parent_focus: true, searchbox: searchbox, id_encode: rcmail.html_identifier_encode, id_decode: rcmail.html_identifier_decode, check_droptarget: function(node) { return !node.virtual && node.id != file_api.env.folder && $.inArray('readonly', node.classes) == -1 && $.inArray('collection', node.classes) == -1; } }); rcmail.folder_list .addEventListener('collapse', function(node) { file_api.folder_collapsed(node); }) .addEventListener('expand', function(node) { file_api.folder_collapsed(node); }) .addEventListener('beforeselect', function(node) { return !rcmail.busy; }) .addEventListener('search', function(search) { file_api.folder_search(search); }) .addEventListener('select', function(node) { if (file_api.search_results_widget) file_api.search_results_widget.select(); file_api.folder_select(node.id); }); // select first folder? if (response.result.auth_errors) { } else if (this.env.folder) rcmail.folder_list.select(this.env.folder); else if (this.env.collection) rcmail.folder_list.select('folder-collection-' + this.env.collection); else if (first) rcmail.folder_list.select(first); rcmail.enable_command('files-create', true); // add tree icons // this.folder_list_tree(this.env.folders); // handle authentication errors on external sources - this.folder_list_auth_errors(response.result); + if (elem.length) + this.folder_list_auth_errors(response.result); }; this.folder_select = function(folder) { if (rcmail.busy) return; var is_collection = folder.match(/^folder-collection-(.*)$/), collection = RegExp.$1 || null; if (is_collection) folder = null; // search-reset can re-select the same folder, skip if (this.env.folder == folder && this.env.collection == collection) return; this.env.folder = folder; this.env.collection = collection; rcmail.enable_command('files-list', true); rcmail.enable_command('files-folder-delete', 'folder-rename', !is_collection); rcmail.enable_command('files-upload', !is_collection && this.is_writable()); rcmail.command('files-list', is_collection ? {collection: collection} : {folder: folder}); this.quota(); }; this.folder_unselect = function() { rcmail.folder_list.select(); this.env.folder = null; this.env.collection = null; rcmail.enable_command('files-folder-delete', 'files-upload', false); }; this.folder_collapsed = function(node) { var prefname = 'kolab_files_collapsed_folders', old = rcmail.env[prefname], entry = '&' + urlencode(node.id) + '&'; if (node.collapsed) { rcmail.env[prefname] = rcmail.env[prefname] + entry; // select the folder if one of its childs is currently selected // don't select if it's virtual (#1488346) if (!node.virtual && this.env.folder && this.env.folder.startsWith(node.id + '/')) { rcmail.folder_list.select(node.id); } } else { rcmail.env[prefname] = rcmail.env[prefname].replace(entry, ''); } if (old !== rcmail.env[prefname] && (!rcmail.file_list || !rcmail.file_list.drag_active)) rcmail.command('save-pref', {name: prefname, value: rcmail.env[prefname]}); }; this.folder_list_row = function(i, folder, parent) { var toggle, sublist, collapsed, parent, parent_name, classes = ['mailbox'], row = $('<li>'), id = 'rcmli' + rcmail.html_identifier_encode(i); row.attr('id', id).append($('<a class="name">').text(folder.name)); if (folder.virtual) classes.push('virtual'); else { if (folder.subscribed !== undefined) row.append(this.folder_list_subscription_button(folder.subscribed)); if (folder.readonly) classes.push('readonly'); } row.addClass(classes.join(' ')); folder.ref = row; if (folder.depth) { // find parent folder parent_name = i.replace(/\/[^/]+$/, ''); if (!parent) parent = $(this.env.folders[parent_name].ref); toggle = $('div.treetoggle', parent); sublist = $('> ul', parent); if (!toggle.length) { collapsed = rcmail.env.kolab_files_collapsed_folders.indexOf('&' + urlencode(parent_name) + '&') > -1; toggle = $('<div>').attr('class', 'treetoggle' + (collapsed ? ' collapsed' : ' expanded')) .html(' ').appendTo(parent); sublist = $('<ul>').attr({role: 'group'}).appendTo(parent); if (collapsed) sublist.hide(); } sublist.append(row); } else { return row; } }; // create subscription button element this.folder_list_subscription_button = function(subscribed) { return $('<a>').attr({ title: rcmail.gettext('kolab_files.listpermanent'), class: 'subscription' + (subscribed ? ' subscribed' : ''), 'aria-checked': subscribed, role: 'checkbox' }); }; // subscription button handler this.folder_list_subscription_button_click = function(elem) { var folder = $(elem).parent('li').prop('id').replace(/^rcmli/, ''), selected = $(elem).hasClass('subscribed'); folder = folder.replace(/--xsR$/, ''); // this might be a search result folder = rcmail.html_identifier_decode(folder); file_api['folder_' + (selected ? 'unsubscribe' : 'subscribe')](folder); return false; }; // sets subscription button status this.folder_list_subscription_state = function(elem, status) { $(elem).children('a.subscription') .prop('aria-checked', status)[status ? 'addClass' : 'removeClass']('subscribed'); }; // Folder searching handler (for unsubscribed folders) this.folder_search = function(search) { // hide search results if (this.search_results_widget) { this.search_results_container.hide(); this.search_results_widget.reset(); } this.search_results = {}; // send search request to the server if (search.query && search.execute) { // cancel previous search request if (this.listsearch_request) { this.listsearch_request.abort(); this.listsearch_request = null; } var params = $.extend({search: search.query, unsubscribed: 1}, this.list_params); this.req = this.set_busy(true, rcmail.gettext('searching')); this.listsearch_request = this.request('folder_list', params, 'folder_search_response'); } else if (!search.query) { if (this.listsearch_request) { this.listsearch_request.abort(); this.listsearch_request = null; } // any subscription changed, make sure the newly added records // are listed before collections not after if (this.folder_subscribe) { var r, last, move = [], rows = $(rcmail.folder_list.container).children('li'); if (rows.length && !$(rows[rows.length-1]).hasClass('collection')) { // collect all folders to move while (rows.length--) { r = $(rows[rows.length]); if (r.hasClass('collection')) last = r; else if (last) break; else move.push(r); } if (last) $.each(move, function() { this.remove(); last.before(this); }); } } } }; // folder search response handler this.folder_search_response = function(response) { if (!this.response(response)) return; var folders = response.result && response.result.list ? response.result.list : response.result; if (!folders.length) return; folders = this.folder_list_parse(folders, 10000, false); if (!this.search_results_widget) { var list = rcmail.folder_list.container, title = rcmail.gettext('kolab_files.additionalfolders'), list_id = list.attr('id') || '0'; this.search_results_container = $('<div class="searchresults"></div>') .append($('<h2 class="boxtitle" id="st:' + list_id + '"></h2>').text(title)) .insertAfter(list); this.search_results_widget = new rcube_treelist_widget('<ul>', { id_prefix: 'rcmli', id_encode: rcmail.html_identifier_encode, id_decode: rcmail.html_identifier_decode, selectable: true }); this.search_results_widget .addEventListener('beforeselect', function(node) { return !rcmail.busy; }) .addEventListener('select', function(node) { rcmail.folder_list.select(); file_api.folder_select(node.id); }); this.search_results_widget.container // copy classes from main list .addClass(list.attr('class')).attr('aria-labelledby', 'st:' + list_id) .appendTo(this.search_results_container) .on('click', 'a.subscription', function(e) { return file_api.folder_list_subscription_button_click(this); }); } // add results to the list $.each(folders, function(i, folder) { var node, separator = file_api.env.directory_separator, path = i.split(separator), classes = ['mailbox'], html = [$('<a>').text(folder.name)]; if (!folder.virtual) { // add subscription button html.push(file_api.folder_list_subscription_button(false)); if (folder.readonly) classes.push('readonly'); } path.pop(); file_api.search_results_widget.insert({ id: i, classes: classes, text: folder.name, html: html, collapsed: false, virtual: folder.virtual }, path.length ? path.join(separator) : null); }); this.search_results = folders; this.search_results_container.show(); }; // folder subscribe request this.folder_subscribe = function(folder) { this.env.folder_subscribe = folder; this.req = this.set_busy(true, 'foldersubscribing'); this.request('folder_subscribe', {folder: folder}, 'folder_subscribe_response'); } // folder subscribe response handler this.folder_subscribe_response = function(response) { if (!this.response(response)) return; this.display_message('foldersubscribed', 'confirmation'); var item, node = rcmail.folder_list.get_item(this.env.folder_subscribe); if (this.search_results && this.search_results[this.env.folder_subscribe]) { item = this.search_results_widget.get_item(this.env.folder_subscribe); this.folder_list_subscription_state(item, true); if (item = $(item).attr('id')) this.folder_list_subscription_state($('#' + item.replace(/--xsR$/, '')), true); } // search result, move from search to main list widget if (!node && this.search_results && this.search_results[this.env.folder_subscribe]) { var i, html, dir, folder, separator = this.env.directory_separator, path = this.env.folder_subscribe.split(separator); // add all folders in a path to the main list if needed // including the subscribed folder for (i=0; i<path.length; i++) { dir = path.slice(0, i + 1).join(separator); node = rcmail.folder_list.get_node(dir); if (!node) { node = this.search_results_widget.get_node(dir); if (!node) { // sanity check return; } if (i == path.length - 1) { item = this.search_results_widget.get_item(dir); this.folder_list_subscription_state(item, true); } folder = this.search_results[dir]; html = [$('<a>').text(folder.name)]; if (!folder.virtual) html.push(this.folder_list_subscription_button(true)); node.html = html; delete node.children; rcmail.folder_list.insert(node, i > 0 ? path.slice(0, i).join(separator) : null); // we're in search result, so there will be two records, // add subscription button to the visible one, it was not cloned if (!folder.virtual) { node = rcmail.folder_list.get_item(dir); $(node).append(file_api.folder_list_subscription_button(true)); } this.env.folders[dir] = folder; } } // now remove them from the search widget while (path.length) { dir = path.join(separator); node = this.search_results_widget.get_item(dir); if ($('ul[role="group"] > li', node).length) break; this.search_results_widget.remove(dir); path.pop(); } node = null; } if (node) this.folder_list_subscription_state(node, true); this.env.folders[this.env.folder_subscribe].subscribed = true; }; // folder unsubscribe request this.folder_unsubscribe = function(folder) { this.env.folder_subscribe = folder; this.req = this.set_busy(true, 'folderunsubscribing'); this.request('folder_unsubscribe', {folder: folder}, 'folder_unsubscribe_response'); } // folder unsubscribe response handler this.folder_unsubscribe_response = function(response) { if (!this.response(response)) return; this.display_message('folderunsubscribed', 'confirmation'); var folder = this.env.folders[this.env.folder_subscribe], node = rcmail.folder_list.get_item(this.env.folder_subscribe); if (this.search_results && this.search_results[this.env.folder_subscribe]) { item = this.search_results_widget.get_item(this.env.folder_subscribe); if (item) { this.folder_list_subscription_state(item, false); item = $('#' + $(item).attr('id').replace(/--xsR$/, '')); } else item = $('#rcmli' + rcmail.html_identifier_encode(this.env.folder_subscribe), rcmail.folder_list.container); this.folder_list_subscription_state(item, false); } this.folder_list_subscription_state(node, false); folder.subscribed = false; }; // folder create request this.folder_create = function(folder) { this.req = this.set_busy(true, 'kolab_files.foldercreating'); this.request('folder_create', {folder: folder}, 'folder_create_response'); }; // folder create response handler this.folder_create_response = function(response) { if (!this.response(response)) return; this.display_message('kolab_files.foldercreatenotice', 'confirmation'); // refresh folders list this.folder_list(); }; // folder rename request this.folder_rename = function(folder, new_name) { if (folder == new_name) return; this.env.folder_rename = new_name; this.req = this.set_busy(true, 'kolab_files.folderupdating'); this.request('folder_move', {folder: folder, 'new': new_name}, 'folder_rename_response'); }; // folder create response handler this.folder_rename_response = function(response) { if (!this.response(response)) return; this.display_message('kolab_files.folderupdatenotice', 'confirmation'); // refresh folders and files list this.env.folder = this.env.folder_rename; this.folder_list(); }; // folder mount (external storage) request this.folder_mount = function(data) { this.req = this.set_busy(true, 'kolab_files.foldermounting'); this.request('folder_create', data, 'folder_mount_response'); }; // folder create response handler this.folder_mount_response = function(response) { if (!this.response(response)) return; this.display_message('kolab_files.foldermountnotice', 'confirmation'); // refresh folders list this.folder_list(); }; // folder delete request this.folder_delete = function(folder) { this.req = this.set_busy(true, 'kolab_files.folderdeleting'); this.request('folder_delete', {folder: folder}, 'folder_delete_response'); }; // folder delete response handler this.folder_delete_response = function(response) { if (!this.response(response)) return; this.env.folder = null; rcmail.enable_command('files-folder-delete', 'folder-rename', 'files-list', false); this.display_message('kolab_files.folderdeletenotice', 'confirmation'); // refresh folders list this.folder_list(); this.quota(); }; // quota request this.quota = function() { if (rcmail.env.files_quota) this.request('quota', {folder: this.env.folder}, 'quota_response'); }; // quota response handler this.quota_response = function(response) { if (!this.response(response)) return; rcmail.files_set_quota(response.result); }; this.file_list = function(params) { if (!rcmail.gui_objects.filelist) return; if (!params) params = {}; // reset all pending list requests for (i in this.requests) { this.requests[i].abort(); rcmail.hide_message(i); delete this.requests[i]; } if (params.all_folders) { params.collection = null; params.folder = null; this.folder_unselect(); } if (params.collection == undefined) params.collection = this.env.collection; if (params.folder == undefined) params.folder = this.env.folder; if (params.sort == undefined) params.sort = this.env.sort_col; if (params.reverse == undefined) params.reverse = this.env.sort_reverse; if (params.search == undefined) params.search = this.env.search; this.env.folder = params.folder; this.env.collection = params.collection; this.env.sort_col = params.sort; this.env.sort_reverse = params.reverse; rcmail.enable_command(rcmail.env.file_commands, false); rcmail.enable_command(rcmail.env.file_commands_all, false); // empty the list this.env.file_list = []; rcmail.file_list.clear(true); // request if (params.collection || params.all_folders) this.file_list_loop(params); else if (this.env.folder) { params.req_id = this.set_busy(true, 'loading'); this.requests[params.req_id] = this.request('file_list', params, 'file_list_response'); } }; // file list response handler this.file_list_response = function(response) { if (response.req_id) rcmail.hide_message(response.req_id); if (!this.response(response)) return; var i = 0, list = [], table = $('#filelist'); $.each(response.result, function(key, data) { var row = file_api.file_list_row(key, data, ++i); rcmail.file_list.insert_row(row); data.row = row; data.filename = key; list.push(data); }); this.env.file_list = list; rcmail.file_list.resize(); // update document sessions info of this folder if (list && list.length) this.request('folder_info', {folder: this.file_path(list[0].filename), sessions: 1}, 'folder_info_response'); }; // call file_list request for every folder (used for search and virt. collections) this.file_list_loop = function(params) { var i, folders = [], limit = Math.max(this.env.search_threads || 1, 1); if (params.collection) { if (!params.search) params.search = {}; params.search['class'] = params.collection; delete params['collection']; } delete params['all_folders']; $.each(this.env.folders, function(i, f) { if (!f.virtual) folders.push(i); }); this.env.folders_loop = folders; this.env.folders_loop_params = params; this.env.folders_loop_lock = false; for (i=0; i<folders.length && i<limit; i++) { params.req_id = this.set_busy(true, 'loading'); params.folder = folders.shift(); this.requests[params.req_id] = this.request('file_list', params, 'file_list_loop_response'); } }; // file list response handler for loop'ed request this.file_list_loop_response = function(response) { var i, folders = this.env.folders_loop, params = this.env.folders_loop_params, limit = Math.max(this.env.search_threads || 1, 1), valid = this.response(response); if (response.req_id) rcmail.hide_message(response.req_id); for (i=0; i<folders.length && i<limit; i++) { params.req_id = this.set_busy(true, 'loading'); params.folder = folders.shift(); this.requests[params.req_id] = this.request('file_list', params, 'file_list_loop_response'); } rcmail.file_list.resize(); if (!valid) return; this.file_list_loop_result_add(response.result); }; // add files from list request to the table (with sorting) this.file_list_loop_result_add = function(result) { // chack if result (hash-array) is empty if (!object_is_empty(result)) return; if (this.env.folders_loop_lock) { setTimeout(function() { file_api.file_list_loop_result_add(result); }, 100); return; } // lock table, other list responses will wait this.env.folders_loop_lock = true; var n, i, len, elem, row, folder, list = [], index = this.env.file_list.length, table = rcmail.file_list; for (n=0, len=index; n<len; n++) { elem = this.env.file_list[n]; for (i in result) { if (this.sort_compare(elem, result[i]) < 0) break; row = this.file_list_row(i, result[i], ++index); table.insert_row(row, elem.row); result[i].row = row; result[i].filename = i; list.push(result[i]); if (!folder) folder = this.file_path(i); delete result[i]; } list.push(elem); } // add the rest of rows $.each(result, function(key, data) { var row = file_api.file_list_row(key, data, ++index); table.insert_row(row); result[key].row = row; result[key].filename = key; list.push(result[key]); if (!folder) folder = file_api.file_path(key); }); this.env.file_list = list; this.env.folders_loop_lock = false; // update document sessions info of this folder if (folder) this.request('folder_info', {folder: folder, sessions: 1}, 'folder_info_response'); }; // sort files list (without API request) this.file_list_sort = function(col, reverse) { var n, len, list = this.env.file_list, table = $('#filelist'), tbody = $('<tbody>'); this.env.sort_col = col; this.env.sort_reverse = reverse; if (!list || !list.length) return; // sort the list list.sort(function (a, b) { return file_api.sort_compare(a, b); }); // add rows to the new body for (n=0, len=list.length; n<len; n++) { tbody.append(list[n].row); } // replace table bodies $('tbody', table).replaceWith(tbody); }; this.file_list_row = function(file, data, index) { var c, col, row = ''; for (c in rcmail.env.file_coltypes) { c = rcmail.env.file_coltypes[c]; if (c == 'name') col = '<td class="name filename ' + this.file_type_class(data.type) + '">' + '<span>' + escapeHTML(data.name) + '</span></td>'; else if (c == 'mtime') col = '<td class="mtime">' + data.mtime + '</td>'; else if (c == 'size') col = '<td class="size">' + this.file_size(data.size) + '</td>'; else if (c == 'options') col = '<td class="options"><span></span></td>'; else col = '<td class="' + c + '"></td>'; row += col; } row = $('<tr>') .html(row) .attr({id: 'rcmrow' + index, 'data-file': file, 'data-type': data.type}); // collection (or search) lists files from all folders // display file name with full path as title if (!this.env.folder) $('td.name span', row).attr('title', file); return row.get(0); }; this.file_search = function(value, all_folders) { if (value) { this.env.search = {name: value}; rcmail.command('files-list', {search: this.env.search, all_folders: all_folders}); } else this.search_reset(); }; this.file_search_reset = function() { if (this.env.search) { this.env.search = null; rcmail.command('files-list'); } }; // handler for folder info response this.folder_info_response = function(response) { if (!this.response(response) || !response.result) return; if (response.result.sessions) - this.sessions[response.folder] = response.result.sessions; + this.sessions[response.result.folder] = response.result.sessions; // update files list with document session info $.each(file_api.env.file_list || [], function(i, file) { var classes = []; $.each(response.result.sessions || [], function(session_id, session) { if (file.filename == session.file) { if ($.inArray('session', classes) < 0) classes.push('session'); if (session.is_owner && $.inArray('owner', classes) < 0) classes.push('owner'); else if (session.is_invited && $.inArray('invited', classes) < 0) classes.push('invited'); } }); $(file.row).attr('class', classes.join(' ')); }); }; this.file_get = function(file, params) { if (!params) params = {}; params.token = this.env.token; params.file = file; rcmail.redirect(this.env.url + this.url('file_get', params)); }; // file(s) delete request this.file_delete = function(files) { this.req = this.set_busy(true, 'kolab_files.filedeleting'); this.request('file_delete', {file: files}, 'file_delete_response'); }; // file(s) delete response handler this.file_delete_response = function(response) { if (!this.response(response)) return; var rco, dir, self = this; this.display_message('kolab_files.filedeletenotice', 'confirmation'); if (rcmail.env.file) { rco = rcmail.opener(); dir = this.file_path(rcmail.env.file); // check if opener window contains files list, if not we can just close current window if (rco && rco.file_list && (opener.file_api.env.folder == dir || !opener.file_api.env.folder)) self = opener.file_api; else window.close(); } // @TODO: consider list modification "in-place" instead of full reload self.file_list(); self.quota(); if (rcmail.env.file) window.close(); }; // file(s) move request this.file_move = function(files, folder) { if (!files || !files.length || !folder) return; var count = 0, list = {}; $.each(files, function(i, v) { var name = folder + file_api.env.directory_separator + file_api.file_name(v); if (name != v) { list[v] = name; count++; } }); if (!count) return; this.req = this.set_busy(true, 'kolab_files.filemoving'); this.request('file_move', {file: list}, 'file_move_response'); }; // file(s) move response handler this.file_move_response = function(response) { if (!this.response(response)) return; if (response.result && response.result.already_exist && response.result.already_exist.length) this.file_move_ask_user(response.result.already_exist, true); else { this.display_message('kolab_files.filemovenotice', 'confirmation'); this.file_list(); } }; // file(s) copy request this.file_copy = function(files, folder) { if (!files || !files.length || !folder) return; var count = 0, list = {}; $.each(files, function(i, v) { var name = folder + file_api.env.directory_separator + file_api.file_name(v); if (name != v) { list[v] = name; count++; } }); if (!count) return; this.req = this.set_busy(true, 'kolab_files.filecopying'); this.request('file_copy', {file: list}, 'file_copy_response'); }; // file(s) copy response handler this.file_copy_response = function(response) { if (!this.response(response)) return; if (response.result && response.result.already_exist && response.result.already_exist.length) this.file_move_ask_user(response.result.already_exist); else { this.display_message('kolab_files.filecopynotice', 'confirmation'); this.quota(); } }; // when file move/copy operation returns file-exists error // this displays a dialog where user can decide to skip // or overwrite destination file(s) this.file_move_ask_user = function(list, move) { var file = list[0], buttons = {}, text = rcmail.gettext('kolab_files.filemoveconfirm').replace('$file', file.dst) dialog = $('<div></div>'); buttons[rcmail.gettext('kolab_files.fileoverwrite')] = function() { var file = list.shift(), f = {}, action = move ? 'file_move' : 'file_copy'; f[file.src] = file.dst; file_api.file_move_ask_list = list; file_api.file_move_ask_mode = move; dialog.dialog('destroy').remove(); file_api.req = file_api.set_busy(true, move ? 'kolab_files.filemoving' : 'kolab_files.filecopying'); file_api.request(action, {file: f, overwrite: 1}, 'file_move_ask_user_response'); }; if (list.length > 1) buttons[rcmail.gettext('kolab_files.fileoverwriteall')] = function() { var f = {}, action = move ? 'file_move' : 'file_copy'; $.each(list, function() { f[this.src] = this.dst; }); dialog.dialog('destroy').remove(); file_api.req = file_api.set_busy(true, move ? 'kolab_files.filemoving' : 'kolab_files.filecopying'); file_api.request(action, {file: f, overwrite: 1}, action + '_response'); }; var skip_func = function() { list.shift(); dialog.dialog('destroy').remove(); if (list.length) file_api.file_move_ask_user(list, move); else if (move) file_api.file_list(); }; buttons[rcmail.gettext('kolab_files.fileskip')] = skip_func; if (list.length > 1) buttons[rcmail.gettext('kolab_files.fileskipall')] = function() { dialog.dialog('destroy').remove(); if (move) file_api.file_list(); }; // open jquery UI dialog kolab_dialog_show(dialog.html(text), { close: skip_func, buttons: buttons, minWidth: 400, width: 400 }); }; // file move (with overwrite) response handler this.file_move_ask_user_response = function(response) { var move = this.file_move_ask_mode, list = this.file_move_ask_list; this.response(response); if (list && list.length) this.file_move_ask_user(list, mode); else { this.display_message('kolab_files.file' + (move ? 'move' : 'copy') + 'notice', 'confirmation'); if (move) this.file_list(); } }; - // file(s) create request - this.file_create = function(file, type, edit) + // file(s) create (or clone) request + this.file_create = function(file, type, edit, cloneof) { this.file_create_edit_file = edit ? file : null; this.file_create_edit_type = edit ? type : null; + this.file_create_folder = this.file_path(file); - this.req = this.set_busy(true, 'kolab_files.filecreating'); - this.request('file_create', {file: file, 'content-type': type, content: ''}, 'file_create_response'); + if (cloneof) { + this.req = this.set_busy(true, 'kolab_files.filecopying'); + this.request('file_copy', {file: cloneof, 'new': file}, 'file_create_response'); + } + else { + this.req = this.set_busy(true, 'kolab_files.filecreating'); + this.request('file_create', {file: file, 'content-type': type, content: ''}, 'file_create_response'); + } }; // file(s) create response handler this.file_create_response = function(response) { if (!this.response(response)) return; // @TODO: we could update metadata instead - this.file_list(); + if (this.file_create_folder == this.env.folder) + this.file_list(); // open the file for editing if editable if (this.file_create_edit_file) { var viewer = this.file_type_supported(this.file_create_edit_type, rcmail.env.files_caps); - this.file_open(this.file_create_edit_file, viewer, 'edit'); + this.file_open(this.file_create_edit_file, viewer, {action: 'edit'}); } }; // file(s) rename request this.file_rename = function(oldfile, newfile) { this.req = this.set_busy(true, 'kolab_files.fileupdating'); this.request('file_move', {file: oldfile, 'new': newfile}, 'file_rename_response'); }; // file(s) move response handler this.file_rename_response = function(response) { if (!this.response(response)) return; // @TODO: we could update metadata instead this.file_list(); }; // file upload request this.file_upload = function(form) { var form = $(form), field = $('input[type=file]', form).get(0), files = field.files ? field.files.length : field.value ? 1 : 0; if (!files || !this.file_upload_size_check(field.files)) return; // submit form and read server response this.file_upload_form(form, 'file_upload', function(event) { var doc, response; try { doc = this.contentDocument ? this.contentDocument : this.contentWindow.document; response = doc.body.innerHTML; // response may be wrapped in <pre> tag if (response.slice(0, 5).toLowerCase() == '<pre>' && response.slice(-6).toLowerCase() == '</pre>') { response = doc.body.firstChild.firstChild.nodeValue; } response = eval('(' + response + ')'); } catch (err) { response = {status: 'ERROR'}; } file_api.file_upload_progress_stop(event.data.ts); // refresh the list on upload success file_api.file_upload_response(response); }); }; // refresh the list on upload success this.file_upload_response = function(response) { if (this.response_parse(response)) { this.file_list(); this.quota(); } }; // check upload max size this.file_upload_size_check = function(files) { var i, size = 0, maxsize = rcmail.env.files_max_upload; if (maxsize && files) { for (i=0; i < files.length; i++) size += files[i].size || files[i].fileSize; if (size > maxsize) { alert(rcmail.get_label('kolab_files.uploadsizeerror').replace('$size', kolab_files_file_size(maxsize))); return false; } } return true; }; // post the given form to a hidden iframe this.file_upload_form = function(form, action, onload) { var ts = new Date().getTime(), frame_name = 'fileupload' + ts; // upload progress support if (rcmail.env.files_progress_name) { var fname = rcmail.env.files_progress_name, field = $('input[name='+fname+']', form); if (!field.length) { field = $('<input>').attr({type: 'hidden', name: fname}); field.prependTo(form); } field.val(ts); this.file_upload_progress(ts, true); } rcmail.display_progress({name: ts}); // have to do it this way for IE // otherwise the form will be posted to a new window if (document.all) { var html = '<iframe id="'+frame_name+'" name="'+frame_name+'"' + ' src="' + rcmail.assets_path('program/resources/blank.gif') + '"' + ' style="width:0;height:0;visibility:hidden;"></iframe>'; document.body.insertAdjacentHTML('BeforeEnd', html); } // for standards-compliant browsers else $('<iframe>') .attr({name: frame_name, id: frame_name}) .css({border: 'none', width: 0, height: 0, visibility: 'hidden'}) .appendTo(document.body); // handle upload errors, parsing iframe content in onload $('#'+frame_name).on('load', {ts:ts}, onload); $(form).attr({ target: frame_name, action: this.env.url + this.url(action, {folder: this.env.folder, token: this.env.token}), method: 'POST' }).attr(form.encoding ? 'encoding' : 'enctype', 'multipart/form-data') .submit(); }; // handler when files are dropped to a designated area. // compose a multipart form data and submit it to the server this.file_drop = function(e) { var files = e.target.files || e.dataTransfer.files; if (!files || !files.length || !this.file_upload_size_check(files)) return; // prepare multipart form data composition var ts = new Date().getTime(), formdata = window.FormData ? new FormData() : null, fieldname = 'file[]', boundary = '------multipartformboundary' + (new Date).getTime(), dashdash = '--', crlf = '\r\n', multipart = dashdash + boundary + crlf; // inline function to submit the files to the server var submit_data = function() { var multiple = files.length > 1; rcmail.display_progress({name: ts}); if (rcmail.env.files_progress_name) file_api.file_upload_progress(ts, true); // complete multipart content and post request multipart += dashdash + boundary + dashdash + crlf; $.ajax({ type: 'POST', dataType: 'json', url: file_api.env.url + file_api.url('file_upload', {folder: file_api.env.folder}), contentType: formdata ? false : 'multipart/form-data; boundary=' + boundary, processData: false, timeout: 0, // disable default timeout set in ajaxSetup() data: formdata || multipart, headers: {'X-Session-Token': file_api.env.token}, success: function(data) { file_api.file_upload_progress_stop(ts); file_api.file_upload_response(data); }, error: function(o, status, err) { file_api.file_upload_progress_stop(ts); rcmail.http_error(o, status, err); }, xhr: function() { var xhr = jQuery.ajaxSettings.xhr(); if (!formdata && xhr.sendAsBinary) xhr.send = xhr.sendAsBinary; return xhr; } }); }; // upload progress supported (and handler exists) // add progress ID to the request - need to be added before files if (rcmail.env.files_progress_name) { if (formdata) formdata.append(rcmail.env.files_progress_name, ts); else multipart += 'Content-Disposition: form-data; name="' + rcmail.env.files_progress_name + '"' + crlf + crlf + ts + crlf + dashdash + boundary + crlf; } // get contents of all dropped files var f, j, i = 0, last = files.length - 1; for (j = 0; j <= last && (f = files[i]); i++) { if (!f.name) f.name = f.fileName; if (!f.size) f.size = f.fileSize; if (!f.type) f.type = 'application/octet-stream'; // file name contains non-ASCII characters, do UTF8-binary string conversion. if (!formdata && /[^\x20-\x7E]/.test(f.name)) f.name_bin = unescape(encodeURIComponent(f.name)); // do it the easy way with FormData (FF 4+, Chrome 5+, Safari 5+) if (formdata) { formdata.append(fieldname, f); if (j == last) return submit_data(); } // use FileReader supporetd by Firefox 3.6 else if (window.FileReader) { var reader = new FileReader(); // closure to pass file properties to async callback function reader.onload = (function(file, j) { return function(e) { multipart += 'Content-Disposition: form-data; name="' + fieldname + '"'; multipart += '; filename="' + (f.name_bin || file.name) + '"' + crlf; multipart += 'Content-Length: ' + file.size + crlf; multipart += 'Content-Type: ' + file.type + crlf + crlf; multipart += reader.result + crlf; multipart += dashdash + boundary + crlf; if (j == last) // we're done, submit the data return submit_data(); } })(f,j); reader.readAsBinaryString(f); } j++; } }; // upload progress requests this.file_upload_progress = function(id, init) { if (init && id) this.uploads[id] = this.env.folder; setTimeout(function() { if (id && file_api.uploads[id]) file_api.request('upload_progress', {id: id}, 'file_upload_progress_response'); }, rcmail.env.files_progress_time * 1000); }; // upload progress response this.file_upload_progress_response = function(response) { if (!this.response(response)) return; var param = response.result; if (!param.id || !this.uploads[param.id]) return; if (param.total) { param.name = param.id; if (!param.done) param.text = kolab_files_progress_str(param); rcmail.display_progress(param); } if (!param.done && param.total) this.file_upload_progress(param.id); else delete this.uploads[param.id]; }; this.file_upload_progress_stop = function(id) { if (id) { delete this.uploads[id]; rcmail.display_progress({name: id}); } }; // open file in new window, using file API viewer - this.file_open = function(file, viewer, action, local) + this.file_open = function(file, viewer, params) { - var href = '?' + $.param({_task: 'files', _action: action || 'open', file: file, viewer: viewer || 0}); + var args = { + _task: 'files', + _action: params && params.action ? params.action : 'open', + _file: file, + _viewer: viewer || 0, + }; + + if (params && params.session) + args._session = params.session; if (rcmail.env.extwin) - href += '&_extwin=1'; + args._extwin = 1; + + href = '?' + $.param(args); - if (local) + if (params && params.local) location.href = href; else rcmail.open_window(href, false, true); }; // save file this.file_save = function(file, content) { rcmail.enable_command('files-save', false); // because we currently can edit only text files // and we do not expect them to be very big, we save // file in a very simple way, no upload progress, etc. this.req = this.set_busy(true, 'saving'); this.request('file_update', {file: file, content: content, info: 1}, 'file_save_response'); }; // file save response handler this.file_save_response = function(response) { rcmail.enable_command('files-save', true); if (!this.response(response)) return; // update file properties table var table = $('#fileinfobox table'), file = response.result; if (file) { $('td.filetype', table).text(file.type); $('td.filesize', table).text(this.file_size(file.size)); $('td.filemtime', table).text(file.mtime); } }; // handle auth errors on folder list this.folder_list_auth_errors = function(result) { if (result && result.auth_errors) { if (!this.auth_errors) this.auth_errors = {}; $.extend(this.auth_errors, result.auth_errors); } // ask for password to the first storage on the list $.each(this.auth_errors || [], function(i, v) { file_api.folder_list_auth_dialog(i, v); return false; }); }; // create dialog for user credentials of external storage this.folder_list_auth_dialog = function(label, driver) { var args = {width: 400, height: 300, buttons: {}}, dialog = $('#files-folder-auth-dialog'), content = this.folder_list_auth_form(driver); dialog.find('table.propform').remove(); $('.auth-options', dialog).before(content); args.buttons[this.t('kolab_files.save')] = function() { var data = {folder: label, list: 1}; $('input', dialog).each(function() { data[this.name] = this.type == 'checkbox' && !this.checked ? '' : this.value; }); file_api.open_dialog = this; file_api.req = file_api.set_busy(true, 'kolab_files.authenticating'); file_api.request('folder_auth', data, 'folder_auth_response'); }; args.buttons[this.t('kolab_files.cancel')] = function() { delete file_api.auth_errors[label]; kolab_dialog_close(this); // go to the next one file_api.folder_list_auth_errors(); }; args.title = this.t('kolab_files.folderauthtitle').replace('$title', label); // show dialog window kolab_dialog_show(dialog, args, function() { // focus first empty input $('input', dialog).each(function() { if (!this.value) { this.focus(); return false; } }); }); }; // folder_auth handler this.folder_auth_response = function(response) { if (!this.response(response)) return; var folders, found, folder = response.result.folder, id = 'rcmli' + rcmail.html_identifier_encode(folder), parent = $('#' + id); // try parent window if the folder element does not exist if (!parent.length && window.parent && window.parent.rcmail) { parent = $('#' + id, window.parent.document.body); } delete this.auth_errors[folder]; kolab_dialog_close(this.open_dialog); // go to the next one this.folder_list_auth_errors(); // parse result folders = this.folder_list_parse(response.result.list); delete folders[folder]; // remove root added in folder_list_parse() // add folders from the external source to the list $.each(folders, function(i, f) { file_api.folder_list_row(i, f, parent.get(0)); found = true; }); // reset folders list widget if (found) rcmail.folder_list.reset(true); // add tree icons // this.folder_list_tree(folders); $.extend(this.env.folders, folders); }; // returns content of the external storage authentication form this.folder_list_auth_form = function(driver) { var rows = []; $.each(driver.form, function(fi, fv) { var id = 'authinput' + fi, attrs = {type: fi.match(/pass/) ? 'password' : 'text', size: 25, name: fi, id: id}, input = $('<input>').attr(attrs); if (driver.form_values && driver.form_values[fi]) input.attr({value: driver.form_values[fi]}); rows.push($('<tr>') .append($('<td class="title">').append($('<label>').attr('for', id).text(fv))) .append($('<td>').append(input)) ); }); return $('<table class="propform">').append(rows); }; }; diff --git a/plugins/kolab_files/lib/kolab_files_engine.php b/plugins/kolab_files/lib/kolab_files_engine.php index 427353df..5607ee63 100644 --- a/plugins/kolab_files/lib/kolab_files_engine.php +++ b/plugins/kolab_files/lib/kolab_files_engine.php @@ -1,1323 +1,1342 @@ <?php /** * Kolab files storage engine * * @version @package_version@ * @author Aleksander Machniak <machniak@kolabsys.com> * * Copyright (C) 2013, Kolab Systems AG <contact@kolabsys.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ class kolab_files_engine { private $plugin; private $rc; private $timeout = 600; private $sort_cols = array('name', 'mtime', 'size'); const API_VERSION = 2; /** * Class constructor */ public function __construct($plugin, $url) { $this->url = rcube_utils::resolve_url($url); $this->plugin = $plugin; $this->rc = $plugin->rc; $this->timeout = $this->rc->config->get('session_lifetime') * 60; } /** * User interface initialization */ public function ui() { $this->plugin->add_texts('localization/'); $templates = array(); // set templates of Files UI and widgets if ($this->rc->task == 'mail') { if (in_array($this->rc->action, array('', 'show', 'compose'))) { $templates[] = 'compose_plugin'; } if (in_array($this->rc->action, array('show', 'preview', 'get'))) { $templates[] = 'message_plugin'; if ($this->rc->action == 'get') { // add "Save as" button into attachment toolbar $this->plugin->add_button(array( 'id' => 'saveas', 'name' => 'saveas', 'type' => 'link', 'onclick' => 'kolab_directory_selector_dialog()', 'class' => 'button buttonPas saveas', 'classact' => 'button saveas', 'label' => 'kolab_files.save', 'title' => 'kolab_files.saveto', ), 'toolbar'); } else { // add "Save as" button into attachment menu $this->plugin->add_button(array( 'id' => 'attachmenusaveas', 'name' => 'attachmenusaveas', 'type' => 'link', 'wrapper' => 'li', 'onclick' => 'return false', 'class' => 'icon active saveas', 'classact' => 'icon active saveas', 'innerclass' => 'icon active saveas', 'label' => 'kolab_files.saveto', ), 'attachmentmenu'); } } $list_widget = true; } else if (!$this->rc->action && in_array($this->rc->task, array('calendar', 'tasks'))) { $list_widget = true; $templates[] = 'compose_plugin'; } else if ($this->rc->task == 'files') { $templates[] = 'files'; // get list of external sources $this->get_external_storage_drivers(); } if ($list_widget) { $this->folder_list_env(); $this->plugin->add_label('save', 'cancel', 'saveto', 'saveall', 'fromcloud', 'attachsel', 'selectfiles', 'attaching', 'collection_audio', 'collection_video', 'collection_image', 'collection_document', 'folderauthtitle', 'authenticating' ); } // add taskbar button if (empty($_REQUEST['framed'])) { $this->plugin->add_button(array( 'command' => 'files', 'class' => 'button-files', 'classsel' => 'button-files button-selected', 'innerclass' => 'button-inner', 'label' => 'kolab_files.files', ), 'taskbar'); } $this->plugin->include_stylesheet($this->plugin->local_skin_path().'/style.css'); if (!empty($templates)) { $collapsed_folders = (string) $this->rc->config->get('kolab_files_collapsed_folders'); $this->plugin->include_script($this->url . '/js/files_api.js'); $this->plugin->include_script('kolab_files.js'); $this->rc->output->include_script('treelist.js'); $this->rc->output->set_env('files_url', $this->url . '/api/'); $this->rc->output->set_env('files_token', $this->get_api_token()); $this->rc->output->set_env('kolab_files_collapsed_folders', $collapsed_folders); // register template objects for dialogs (and main interface) $this->rc->output->add_handlers(array( 'folder-create-form' => array($this, 'folder_create_form'), 'folder-edit-form' => array($this, 'folder_edit_form'), 'folder-mount-form' => array($this, 'folder_mount_form'), 'folder-auth-options'=> array($this, 'folder_auth_options'), 'file-search-form' => array($this, 'file_search_form'), 'file-rename-form' => array($this, 'file_rename_form'), 'file-create-form' => array($this, 'file_create_form'), + 'file-edit-dialog' => array($this, 'file_edit_dialog'), 'filelist' => array($this, 'file_list'), 'filequotadisplay' => array($this, 'quota_display'), )); if ($this->rc->task != 'files') { // add dialog(s) content at the end of page body foreach ($templates as $template) { $this->rc->output->add_footer( $this->rc->output->parse('kolab_files.' . $template, false, false)); } } } } /** * Engine actions handler */ public function actions() { if ($this->rc->task == 'files' && $this->rc->action) { $action = $this->rc->action; } else if ($this->rc->task != 'files' && $_POST['act']) { $action = $_POST['act']; } else { $action = 'index'; } $method = 'action_' . str_replace('-', '_', $action); if (method_exists($this, $method)) { $this->plugin->add_texts('localization/'); $this->{$method}(); } } /** * Template object for folder creation form */ public function folder_create_form($attrib) { $attrib['name'] = 'folder-create-form'; if (empty($attrib['id'])) { $attrib['id'] = 'folder-create-form'; } $input_name = new html_inputfield(array('id' => 'folder-name', 'name' => 'name', 'size' => 30)); $select_parent = new html_select(array('id' => 'folder-parent', 'name' => 'parent')); $table = new html_table(array('cols' => 2, 'class' => 'propform')); $table->add('title', html::label('folder-name', rcube::Q($this->plugin->gettext('foldername')))); $table->add(null, $input_name->show()); $table->add('title', html::label('folder-parent', rcube::Q($this->plugin->gettext('folderinside')))); $table->add(null, $select_parent->show()); $out = $table->show(); // add form tag around text field if (empty($attrib['form'])) { $out = $this->rc->output->form_tag($attrib, $out); } $this->plugin->add_label('foldercreating', 'foldercreatenotice', 'create', 'foldercreate', 'cancel'); $this->rc->output->add_gui_object('folder-create-form', $attrib['id']); return $out; } /** * Template object for folder editing form */ public function folder_edit_form($attrib) { $attrib['name'] = 'folder-edit-form'; if (empty($attrib['id'])) { $attrib['id'] = 'folder-edit-form'; } $input_name = new html_inputfield(array('id' => 'folder-edit-name', 'name' => 'name', 'size' => 30)); $select_parent = new html_select(array('id' => 'folder-edit-parent', 'name' => 'parent')); $table = new html_table(array('cols' => 2, 'class' => 'propform')); $table->add('title', html::label('folder-name', rcube::Q($this->plugin->gettext('foldername')))); $table->add(null, $input_name->show()); $table->add('title', html::label('folder-parent', rcube::Q($this->plugin->gettext('folderinside')))); $table->add(null, $select_parent->show()); $out = $table->show(); // add form tag around text field if (empty($attrib['form'])) { $out = $this->rc->output->form_tag($attrib, $out); } $this->plugin->add_label('folderupdating', 'folderupdatenotice', 'save', 'folderedit', 'cancel'); $this->rc->output->add_gui_object('folder-edit-form', $attrib['id']); return $out; } /** * Template object for folder mounting form */ public function folder_mount_form($attrib) { $sources = $this->rc->output->get_env('external_sources'); if (empty($sources) || !is_array($sources)) { return ''; } $attrib['name'] = 'folder-mount-form'; if (empty($attrib['id'])) { $attrib['id'] = 'folder-mount-form'; } // build form content $table = new html_table(array('cols' => 2, 'class' => 'propform')); $input_name = new html_inputfield(array('id' => 'folder-mount-name', 'name' => 'name', 'size' => 30)); $input_driver = new html_radiobutton(array('name' => 'driver', 'size' => 30)); $table->add('title', html::label('folder-mount-name', rcube::Q($this->plugin->gettext('name')))); $table->add(null, $input_name->show()); foreach ($sources as $key => $source) { $id = 'source-' . $key; $form = new html_table(array('cols' => 2, 'class' => 'propform driverform')); foreach ((array) $source['form'] as $idx => $label) { $iid = $id . '-' . $idx; $type = stripos($idx, 'pass') !== false ? 'html_passwordfield' : 'html_inputfield'; $input = new $type(array('size' => 30)); $form->add('title', html::label($iid, rcube::Q($label))); $form->add(null, $input->show('', array( 'id' => $iid, 'name' => $key . '[' . $idx . ']' ))); } $row = $input_driver->show(null, array('value' => $key)) . html::img(array('src' => $source['image'], 'alt' => $key, 'title' => $source['name'])) . html::div(null, html::span('name', rcube::Q($source['name'])) . html::br() . html::span('description', rcube::Q($source['description'])) . $form->show() ); $table->add(array('id' => $id, 'colspan' => 2, 'class' => 'source'), $row); } $out = $table->show() . $this->folder_auth_options(array('suffix' => '-form')); // add form tag around text field if (empty($attrib['form'])) { $out = $this->rc->output->form_tag($attrib, $out); } $this->plugin->add_label('foldermounting', 'foldermountnotice', 'foldermount', 'save', 'cancel', 'folderauthtitle', 'authenticating' ); $this->rc->output->add_gui_object('folder-mount-form', $attrib['id']); return $out; } /** * Template object for folder authentication options */ public function folder_auth_options($attrib) { $checkbox = new html_checkbox(array( 'name' => 'store_passwords', 'value' => '1', 'id' => 'auth-pass-checkbox' . $attrib['suffix'], )); return html::div('auth-options', $checkbox->show(). ' ' . html::label('auth-pass-checkbox' . $attrib['suffix'], $this->plugin->gettext('storepasswords')) . html::span('description', $this->plugin->gettext('storepasswordsdesc')) ); } + /** + * Template object for file edit dialog/warnings + */ + public function file_edit_dialog($attrib) + { + $this->plugin->add_label('select', 'create', 'cancel', 'editfiledialog', 'editfilesessions', + 'newsession', 'ownedsession', 'invitedsession', 'joinsession', 'editfilero', 'editfilerotitle', + 'newsessionro' + ); + + return '<div></div>'; + } + /** * Template object for file_rename form */ public function file_rename_form($attrib) { $attrib['name'] = 'file-rename-form'; if (empty($attrib['id'])) { $attrib['id'] = 'file-rename-form'; } $input_name = new html_inputfield(array('id' => 'file-rename-name', 'name' => 'name', 'size' => 50)); $table = new html_table(array('cols' => 2, 'class' => 'propform')); $table->add('title', html::label('file-rename-name', rcube::Q($this->plugin->gettext('filename')))); $table->add(null, $input_name->show()); $out = $table->show(); // add form tag around text field if (empty($attrib['form'])) { $out = $this->rc->output->form_tag($attrib, $out); } $this->plugin->add_label('save', 'cancel', 'fileupdating', 'renamefile'); $this->rc->output->add_gui_object('file-rename-form', $attrib['id']); return $out; } /** * Template object for file_create form */ public function file_create_form($attrib) { $attrib['name'] = 'file-create-form'; if (empty($attrib['id'])) { $attrib['id'] = 'file-create-form'; } $input_name = new html_inputfield(array('id' => 'file-create-name', 'name' => 'name', 'size' => 30)); $select_parent = new html_select(array('id' => 'file-create-parent', 'name' => 'parent')); $select_type = new html_select(array('id' => 'file-create-type', 'name' => 'type')); $table = new html_table(array('cols' => 2, 'class' => 'propform')); // @TODO: get this list from Chwala API $types = array( 'application/vnd.oasis.opendocument.text' => 'odt', 'text/plain' => 'txt', 'text/html' => 'html', ); foreach (array_keys($types) as $type) { list ($app, $label) = explode('/', $type); $label = preg_replace('/[^a-z]/', '', $label); $select_type->add($this->plugin->gettext('type.' . $label), $type); } $table->add('title', html::label('file-create-name', rcube::Q($this->plugin->gettext('filename')))); $table->add(null, $input_name->show()); $table->add('title', html::label('file-create-type', rcube::Q($this->plugin->gettext('type')))); $table->add(null, $select_type->show()); $table->add('title', html::label('folder-parent', rcube::Q($this->plugin->gettext('folderinside')))); $table->add(null, $select_parent->show()); $out = $table->show(); // add form tag around text field if (empty($attrib['form'])) { $out = $this->rc->output->form_tag($attrib, $out); } - $this->plugin->add_label('create', 'cancel', 'filecreating', 'createfile', 'createandedit'); + $this->plugin->add_label('create', 'cancel', 'filecreating', 'createfile', 'createandedit', + 'copyfile', 'copyandedit'); $this->rc->output->add_gui_object('file-create-form', $attrib['id']); $this->rc->output->set_env('file_extensions', $types); return $out; } /** * Template object for file search form in "From cloud" dialog */ public function file_search_form($attrib) { $attrib['name'] = '_q'; if (empty($attrib['id'])) { $attrib['id'] = 'filesearchbox'; } if ($attrib['type'] == 'search' && !$this->rc->output->browser->khtml) { unset($attrib['type'], $attrib['results']); } $input_q = new html_inputfield($attrib); $out = $input_q->show(); // add some labels to client $this->rc->output->add_label('searching'); $this->rc->output->add_gui_object('filesearchbox', $attrib['id']); // add form tag around text field if (empty($attrib['form'])) { $out = $this->rc->output->form_tag(array( 'action' => '?_task=files', 'name' => "filesearchform", 'onsubmit' => rcmail_output::JS_OBJECT_NAME . ".command('files-search'); return false", ), $out); } return $out; } /** * Template object for files list */ public function file_list($attrib) { // define list of cols to be displayed based on parameter or config if (empty($attrib['columns'])) { $list_cols = $this->rc->config->get('kolab_files_list_cols'); $dont_override = $this->rc->config->get('dont_override'); $a_show_cols = is_array($list_cols) ? $list_cols : array('name'); $this->rc->output->set_env('col_movable', !in_array('kolab_files_list_cols', (array)$dont_override)); } else { $columns = str_replace(array("'", '"'), '', $attrib['columns']); $a_show_cols = preg_split('/[\s,;]+/', $columns); } // make sure 'name' and 'options' column is present if (!in_array('name', $a_show_cols)) { array_unshift($a_show_cols, 'name'); } if (!in_array('options', $a_show_cols)) { array_unshift($a_show_cols, 'options'); } $attrib['columns'] = $a_show_cols; // save some variables for use in ajax list $_SESSION['kolab_files_list_attrib'] = $attrib; // For list in dialog(s) remove all option-like columns if ($this->rc->task != 'files') { $a_show_cols = array_intersect($a_show_cols, $this->sort_cols); } // set default sort col/order to session if (!isset($_SESSION['kolab_files_sort_col'])) $_SESSION['kolab_files_sort_col'] = $this->rc->config->get('kolab_files_sort_col') ?: 'name'; if (!isset($_SESSION['kolab_files_sort_order'])) $_SESSION['kolab_files_sort_order'] = strtoupper($this->rc->config->get('kolab_files_sort_order') ?: 'asc'); // set client env $this->rc->output->add_gui_object('filelist', $attrib['id']); $this->rc->output->set_env('sort_col', $_SESSION['kolab_files_sort_col']); $this->rc->output->set_env('sort_order', $_SESSION['kolab_files_sort_order']); $this->rc->output->set_env('file_coltypes', $a_show_cols); $this->rc->output->set_env('search_threads', $this->rc->config->get('kolab_files_search_threads')); $this->rc->output->include_script('list.js'); // attach css rules for mimetype icons $this->plugin->include_stylesheet($this->url . '/skins/default/images/mimetypes/style.css'); $thead = ''; foreach ($this->file_list_head($attrib, $a_show_cols) as $cell) { $thead .= html::tag('th', array('class' => $cell['className'], 'id' => $cell['id']), $cell['html']); } return html::tag('table', $attrib, html::tag('thead', null, html::tag('tr', null, $thead)) . html::tag('tbody', null, ''), array('style', 'class', 'id', 'cellpadding', 'cellspacing', 'border', 'summary')); } /** * Creates <THEAD> for message list table */ protected function file_list_head($attrib, $a_show_cols) { $skin_path = $_SESSION['skin_path']; // $image_tag = html::img(array('src' => "%s%s", 'alt' => "%s")); // check to see if we have some settings for sorting $sort_col = $_SESSION['kolab_files_sort_col']; $sort_order = $_SESSION['kolab_files_sort_order']; $dont_override = (array)$this->rc->config->get('dont_override'); $disabled_sort = in_array('message_sort_col', $dont_override); $disabled_order = in_array('message_sort_order', $dont_override); $this->rc->output->set_env('disabled_sort_col', $disabled_sort); $this->rc->output->set_env('disabled_sort_order', $disabled_order); // define sortable columns if ($disabled_sort) $a_sort_cols = $sort_col && !$disabled_order ? array($sort_col) : array(); else $a_sort_cols = $this->sort_cols; if (!empty($attrib['optionsmenuicon'])) { $onclick = 'return ' . rcmail_output::JS_OBJECT_NAME . ".command('menu-open', 'filelistmenu', this, event)"; $inner = $this->rc->gettext('listoptions'); if (is_string($attrib['optionsmenuicon']) && $attrib['optionsmenuicon'] != 'true') { $inner = html::img(array('src' => $skin_path . $attrib['optionsmenuicon'], 'alt' => $this->rc->gettext('listoptions'))); } $list_menu = html::a(array( 'href' => '#list-options', 'onclick' => $onclick, 'class' => 'listmenu', 'id' => 'listmenulink', 'title' => $this->rc->gettext('listoptions'), 'tabindex' => '0', ), $inner); } else { $list_menu = ''; } $cells = array(); foreach ($a_show_cols as $col) { // get column name switch ($col) { /* case 'status': $col_name = '<span class="' . $col .'"> </span>'; break; */ case 'options': $col_name = $list_menu; break; default: $col_name = rcube::Q($this->plugin->gettext($col)); } // make sort links if (in_array($col, $a_sort_cols)) { $col_name = html::a(array( 'href' => "#sort", 'onclick' => 'return ' . rcmail_output::JS_OBJECT_NAME . ".command('files-sort','$col',this)", 'title' => $this->plugin->gettext('sortby') ), $col_name); } else if ($col_name[0] != '<') $col_name = '<span class="' . $col .'">' . $col_name . '</span>'; $sort_class = $col == $sort_col && !$disabled_order ? " sorted$sort_order" : ''; $class_name = $col.$sort_class; // put it all together $cells[] = array('className' => $class_name, 'id' => "rcm$col", 'html' => $col_name); } return $cells; } /** * Update files list object */ protected function file_list_update($prefs) { $attrib = $_SESSION['kolab_files_list_attrib']; if (!empty($prefs['kolab_files_list_cols'])) { $attrib['columns'] = $prefs['kolab_files_list_cols']; $_SESSION['kolab_files_list_attrib'] = $attrib; } $a_show_cols = $attrib['columns']; $head = ''; foreach ($this->file_list_head($attrib, $a_show_cols) as $cell) { $head .= html::tag('td', array('class' => $cell['className'], 'id' => $cell['id']), $cell['html']); } $head = html::tag('tr', null, $head); $this->rc->output->set_env('file_coltypes', $a_show_cols); $this->rc->output->command('files_list_update', $head); } /** * Template object for file info box */ public function file_info_box($attrib) { // print_r($this->file_data, true); $table = new html_table(array('cols' => 2, 'class' => $attrib['class'])); // file name $table->add('label', $this->plugin->gettext('name').':'); $table->add('data filename', $this->file_data['name']); // file type // @TODO: human-readable type name $table->add('label', $this->plugin->gettext('type').':'); $table->add('data filetype', $this->file_data['type']); // file size $table->add('label', $this->plugin->gettext('size').':'); $table->add('data filesize', $this->rc->show_bytes($this->file_data['size'])); // file modification time $table->add('label', $this->plugin->gettext('mtime').':'); $table->add('data filemtime', $this->file_data['mtime']); // @TODO: for images: width, height, color depth, etc. // @TODO: for text files: count of characters, lines, words return $table->show(); } /** * Template object for file preview frame */ public function file_preview_frame($attrib) { if (empty($attrib['id'])) { $attrib['id'] = 'filepreviewframe'; } if ($frame = $this->file_data['viewer']['frame']) { return $frame; } if ($href = $this->file_data['viewer']['href']) { // file href attribute must be an absolute URL (Bug #2063) if (!empty($href)) { if (!preg_match('|^https?://|', $href)) { $href = $this->url . '/api/' . $href; } } } else { $token = $this->get_api_token(); $href = $this->url . '/api/?method=file_get' . '&file=' . urlencode($this->file_data['filename']) . '&token=' . urlencode($token); } $this->rc->output->add_gui_object('preview_frame', $attrib['id']); $attrib['allowfullscreen'] = true; $attrib['src'] = $href; $attrib['onload'] = 'kolab_files_frame_load(this)'; return html::iframe($attrib); } /** * Template object for quota display */ public function quota_display($attrib) { if (!$attrib['id']) { $attrib['id'] = 'rcmquotadisplay'; } $quota_type = !empty($attrib['display']) ? $attrib['display'] : 'text'; $this->rc->output->add_gui_object('quotadisplay', $attrib['id']); $this->rc->output->set_env('quota_type', $quota_type); // get quota $token = $this->get_api_token(); $request = $this->get_request(array('method' => 'quota'), $token); // send request to the API try { $response = $request->send(); $status = $response->getStatus(); $body = @json_decode($response->getBody(), true); if ($status == 200 && $body['status'] == 'OK') { $quota = $body['result']; } else { throw new Exception($body['reason']); } } catch (Exception $e) { rcube::raise_error($e, true, false); $quota = array('total' => 0, 'percent' => 0); } $quota = rcube_output::json_serialize($quota); $this->rc->output->add_script(rcmail_output::JS_OBJECT_NAME . ".files_set_quota($quota);", 'docready'); return html::span($attrib, ''); } /** * Get API token for current user session, authenticate if needed */ public function get_api_token() { $token = $_SESSION['kolab_files_token']; $time = $_SESSION['kolab_files_time']; if ($token && time() - $this->timeout < $time) { if (time() - $time <= $this->timeout / 2) { return $token; } } $request = $this->get_request(array('method' => 'ping'), $token); try { $url = $request->getUrl(); // Send ping request if ($token) { $url->setQueryVariables(array('method' => 'ping')); $request->setUrl($url); $response = $request->send(); $status = $response->getStatus(); if ($status == 200 && ($body = json_decode($response->getBody(), true))) { if ($body['status'] == 'OK') { $_SESSION['kolab_files_time'] = time(); return $token; } } } // Go with authenticate request $url->setQueryVariables(array('method' => 'authenticate', 'version' => self::API_VERSION)); $request->setUrl($url); $request->setAuth($this->rc->user->get_username(), $this->rc->decrypt($_SESSION['password'])); $response = $request->send(); $status = $response->getStatus(); if ($status == 200 && ($body = json_decode($response->getBody(), true))) { $token = $body['result']['token']; if ($token) { $_SESSION['kolab_files_token'] = $token; $_SESSION['kolab_files_time'] = time(); $_SESSION['kolab_files_caps'] = $body['result']['capabilities']; } } else { throw new Exception(sprintf("Authenticate error (Status: %d)", $status)); } } catch (Exception $e) { rcube::raise_error($e, true, false); } return $token; } /** * Initialize HTTP_Request object */ protected function get_request($get = null, $token = null) { $url = $this->url . '/api/'; if (!$this->request) { $config = array( 'store_body' => true, 'follow_redirects' => true, ); $this->request = libkolab::http_request($url, 'GET', $config); } else { // cleanup try { $this->request->setBody(''); $this->request->setUrl($url); $this->request->setMethod(HTTP_Request2::METHOD_GET); } catch (Exception $e) { rcube::raise_error($e, true, true); } } if ($token) { $this->request->setHeader('X-Session-Token', $token); } if (!empty($get)) { $url = $this->request->getUrl(); $url->setQueryVariables($get); $this->request->setUrl($url); } // some HTTP server configurations require this header $this->request->setHeader('accept', "application/json,text/javascript,*/*"); return $this->request; } /** * Handler for main files interface (Files task) */ protected function action_index() { $this->plugin->add_label( 'uploading', 'attaching', 'searching', 'uploadsizeerror', 'filedeleting', 'filedeletenotice', 'filedeleteconfirm', 'filemoving', 'filemovenotice', 'filemoveconfirm', 'filecopying', 'filecopynotice', 'fileskip', 'fileskipall', 'fileoverwrite', 'fileoverwriteall' ); $this->folder_list_env(); $this->rc->output->add_label('uploadprogress', 'GB', 'MB', 'KB', 'B'); $this->rc->output->set_pagetitle($this->plugin->gettext('files')); $this->rc->output->set_env('file_mimetypes', $this->get_mimetypes()); $this->rc->output->set_env('files_quota', $_SESSION['kolab_files_caps']['QUOTA']); $this->rc->output->set_env('files_max_upload', $_SESSION['kolab_files_caps']['MAX_UPLOAD']); $this->rc->output->set_env('files_progress_name', $_SESSION['kolab_files_caps']['PROGRESS_NAME']); $this->rc->output->set_env('files_progress_time', $_SESSION['kolab_files_caps']['PROGRESS_TIME']); $this->rc->output->send('kolab_files.files'); } /** * Handler for preferences save action */ protected function action_prefs() { $dont_override = (array)$this->rc->config->get('dont_override'); $prefs = array(); $opts = array( 'kolab_files_sort_col' => true, 'kolab_files_sort_order' => true, 'kolab_files_list_cols' => false, ); foreach ($opts as $o => $sess) { if (isset($_POST[$o]) && !in_array($o, $dont_override)) { $prefs[$o] = rcube_utils::get_input_value($o, rcube_utils::INPUT_POST); if ($sess) { $_SESSION[$o] = $prefs[$o]; } if ($o == 'kolab_files_list_cols') { $update_list = true; } } } // save preference values if (!empty($prefs)) { $this->rc->user->save_prefs($prefs); } if (!empty($update_list)) { $this->file_list_update($prefs); } $this->rc->output->send(); } /** * Handler for file open action */ protected function action_open() { $this->rc->output->set_env('files_caps', $_SESSION['kolab_files_caps']); - $this->file_opener(intval($_GET['viewer']) & ~4); + $this->rc->output->set_env('file_mimetypes', $this->get_mimetypes()); + + $this->file_opener(intval($_GET['_viewer']) & ~4); } /** * Handler for file open action */ protected function action_edit() { - $this->file_opener(intval($_GET['viewer'])); + $this->file_opener(intval($_GET['_viewer'])); } /** * Handler for "save all attachments into cloud" action */ protected function action_save_file() { // $source = rcube_utils::get_input_value('source', rcube_utils::INPUT_POST); $uid = rcube_utils::get_input_value('uid', rcube_utils::INPUT_POST); $dest = rcube_utils::get_input_value('dest', rcube_utils::INPUT_POST); $id = rcube_utils::get_input_value('id', rcube_utils::INPUT_POST); $name = rcube_utils::get_input_value('name', rcube_utils::INPUT_POST); $temp_dir = unslashify($this->rc->config->get('temp_dir')); $message = new rcube_message($uid); $request = $this->get_request(); $url = $request->getUrl(); $files = array(); $errors = array(); $attachments = array(); $request->setMethod(HTTP_Request2::METHOD_POST); $request->setHeader('X-Session-Token', $this->get_api_token()); $url->setQueryVariables(array('method' => 'file_upload', 'folder' => $dest)); $request->setUrl($url); foreach ($message->attachments as $attach_prop) { if (empty($id) || $id == $attach_prop->mime_id) { $filename = strlen($name) ? $name : rcmail_attachment_name($attach_prop, true); $attachments[$filename] = $attach_prop; } } // @TODO: handle error // @TODO: implement file upload using file URI instead of body upload foreach ($attachments as $attach_name => $attach_prop) { $path = tempnam($temp_dir, 'rcmAttmnt'); // save attachment to file if ($fp = fopen($path, 'w+')) { $message->get_part_body($attach_prop->mime_id, false, 0, $fp); } else { $errors[] = true; rcube::raise_error(array( 'code' => 500, 'type' => 'php', 'line' => __LINE__, 'file' => __FILE__, 'message' => "Unable to save attachment into file $path"), true, false); continue; } fclose($fp); // send request to the API try { $request->setBody(''); $request->addUpload('file[]', $path, $attach_name, $attach_prop->mimetype); $response = $request->send(); $status = $response->getStatus(); $body = @json_decode($response->getBody(), true); if ($status == 200 && $body['status'] == 'OK') { $files[] = $attach_name; } else { throw new Exception($body['reason']); } } catch (Exception $e) { unlink($path); $errors[] = $e->getMessage(); rcube::raise_error(array( 'code' => 500, 'type' => 'php', 'line' => __LINE__, 'file' => __FILE__, 'message' => $e->getMessage()), true, false); continue; } // clean up unlink($path); $request->setBody(''); } if ($count = count($files)) { $msg = $this->plugin->gettext(array('name' => 'saveallnotice', 'vars' => array('n' => $count))); $this->rc->output->show_message($msg, 'confirmation'); } if ($count = count($errors)) { $msg = $this->plugin->gettext(array('name' => 'saveallerror', 'vars' => array('n' => $count))); $this->rc->output->show_message($msg, 'error'); } // @TODO: update quota indicator, make this optional in case files aren't stored in IMAP $this->rc->output->send(); } /** * Handler for "add attachments from the cloud" action */ protected function action_attach_file() { $files = rcube_utils::get_input_value('files', rcube_utils::INPUT_POST); $uploadid = rcube_utils::get_input_value('uploadid', rcube_utils::INPUT_POST); $COMPOSE_ID = rcube_utils::get_input_value('id', rcube_utils::INPUT_POST); $COMPOSE = null; $errors = array(); $attachments = array(); if ($this->rc->task == 'mail') { if ($COMPOSE_ID && $_SESSION['compose_data_'.$COMPOSE_ID]) { $COMPOSE =& $_SESSION['compose_data_'.$COMPOSE_ID]; } if (!$COMPOSE) { die("Invalid session var!"); } // attachment upload action if (!is_array($COMPOSE['attachments'])) { $COMPOSE['attachments'] = array(); } } // clear all stored output properties (like scripts and env vars) $this->rc->output->reset(); $temp_dir = unslashify($this->rc->config->get('temp_dir')); $request = $this->get_request(); $url = $request->getUrl(); // Use observer object to store HTTP response into a file require_once $this->plugin->home . DIRECTORY_SEPARATOR . 'lib' . DIRECTORY_SEPARATOR . 'kolab_files_observer.php'; $observer = new kolab_files_observer(); $request->setHeader('X-Session-Token', $this->get_api_token()); // download files from the API and attach them foreach ($files as $file) { // decode filename $file = urldecode($file); // get file information try { $url->setQueryVariables(array('method' => 'file_info', 'file' => $file)); $request->setUrl($url); $response = $request->send(); $status = $response->getStatus(); $body = @json_decode($response->getBody(), true); if ($status == 200 && $body['status'] == 'OK') { $file_params = $body['result']; } else { throw new Exception($body['reason']); } } catch (Exception $e) { $errors[] = $e->getMessage(); rcube::raise_error(array( 'code' => 500, 'type' => 'php', 'line' => __LINE__, 'file' => __FILE__, 'message' => $e->getMessage()), true, false); continue; } // set location of downloaded file $path = tempnam($temp_dir, 'rcmAttmnt'); $observer->set_file($path); // download file try { $url->setQueryVariables(array('method' => 'file_get', 'file' => $file)); $request->setUrl($url); $request->attach($observer); $response = $request->send(); $status = $response->getStatus(); $response->getBody(); // returns nothing $request->detach($observer); if ($status != 200 || !file_exists($path)) { throw new Exception("Unable to save file"); } } catch (Exception $e) { $errors[] = $e->getMessage(); rcube::raise_error(array( 'code' => 500, 'type' => 'php', 'line' => __LINE__, 'file' => __FILE__, 'message' => $e->getMessage()), true, false); continue; } $attachment = array( 'path' => $path, 'size' => $file_params['size'], 'name' => $file_params['name'], 'mimetype' => $file_params['type'], 'group' => $COMPOSE_ID, ); if ($this->rc->task != 'mail') { $attachments[] = $attachment; continue; } $attachment = $this->rc->plugins->exec_hook('attachment_save', $attachment); if ($attachment['status'] && !$attachment['abort']) { $id = $attachment['id']; // store new attachment in session unset($attachment['data'], $attachment['status'], $attachment['abort']); $COMPOSE['attachments'][$id] = $attachment; if (($icon = $COMPOSE['deleteicon']) && is_file($icon)) { $button = html::img(array( 'src' => $icon, 'alt' => $this->rc->gettext('delete') )); } else { $button = rcube::Q($this->rc->gettext('delete')); } $content = html::a(array( 'href' => "#delete", 'onclick' => sprintf("return %s.command('remove-attachment','rcmfile%s', this)", rcmail_output::JS_OBJECT_NAME, $id), 'title' => $this->rc->gettext('delete'), 'class' => 'delete', ), $button); $content .= rcube::Q($attachment['name']); $this->rc->output->command('add2attachment_list', "rcmfile$id", array( 'html' => $content, 'name' => $attachment['name'], 'mimetype' => $attachment['mimetype'], 'classname' => rcmail_filetype2classname($attachment['mimetype'], $attachment['name']), 'complete' => true), $uploadid); } else if ($attachment['error']) { $errors[] = $attachment['error']; } else { $errors[] = $this->plugin->gettext('attacherror'); } } if (!empty($errors)) { $this->rc->output->command('display_message', $this->plugin->gettext('attacherror'), 'error'); $this->rc->output->command('remove_from_attachment_list', $uploadid); } else if ($this->rc->task == 'calendar' || $this->rc->task == 'tasks') { // for uploads in events/tasks we'll use its standard upload handler, // for this we have to fake $_FILES and some other POST args foreach ($attachments as $attach) { $_FILES['_attachments']['tmp_name'][] = $attachment['path']; $_FILES['_attachments']['name'][] = $attachment['name']; $_FILES['_attachments']['size'][] = $attachment['size']; $_FILES['_attachments']['type'][] = $attachment['mimetype']; $_FILES['_attachments']['error'][] = null; } $_GET['_uploadid'] = $uploadid; $_GET['_id'] = $COMPOSE_ID; switch ($this->rc->task) { case 'tasks': libcalendaring::get_instance()->attachment_upload(tasklist::SESSION_KEY); break; case 'calendar': libcalendaring::get_instance()->attachment_upload(calendar::SESSION_KEY, 'cal-'); break; } } // send html page with JS calls as response $this->rc->output->command('auto_save_start', false); $this->rc->output->send(); } /** * Handler for file open/edit action */ protected function file_opener($viewer) { - $file = rcube_utils::get_input_value('file', rcube_utils::INPUT_GET); + $file = rcube_utils::get_input_value('_file', rcube_utils::INPUT_GET); + $session = rcube_utils::get_input_value('_session', rcube_utils::INPUT_GET); // get file info $token = $this->get_api_token(); $request = $this->get_request(array( - 'method' => 'file_info', - 'file' => $file, - 'viewer' => $viewer, + 'method' => 'file_info', + 'file' => $file, + 'viewer' => $viewer, + 'session' => $session, ), $token); // send request to the API try { $response = $request->send(); $status = $response->getStatus(); $body = @json_decode($response->getBody(), true); if ($status == 200 && $body['status'] == 'OK') { $this->file_data = $body['result']; } else { throw new Exception($body['reason']); } } catch (Exception $e) { rcube::raise_error(array( 'code' => 500, 'type' => 'php', 'line' => __LINE__, 'file' => __FILE__, 'message' => $e->getMessage()), true, true); } $this->file_data['filename'] = $file; $this->plugin->add_label('filedeleteconfirm', 'filedeleting', 'filedeletenotice'); // register template objects for dialogs (and main interface) $this->rc->output->add_handlers(array( 'fileinfobox' => array($this, 'file_info_box'), 'filepreviewframe' => array($this, 'file_preview_frame'), )); $placeholder = $this->rc->output->asset_url('program/resources/blank.gif'); // this one is for styling purpose $this->rc->output->set_env('extwin', true); $this->rc->output->set_env('file', $file); $this->rc->output->set_env('file_data', $this->file_data); $this->rc->output->set_env('photo_placeholder', $placeholder); $this->rc->output->set_pagetitle(rcube::Q($file)); $this->rc->output->send('kolab_files.' . ($viewer & 4 ? 'docedit' : 'filepreview')); } /** * Returns mimetypes supported by File API viewers */ protected function get_mimetypes() { $token = $this->get_api_token(); $request = $this->get_request(array('method' => 'mimetypes'), $token); // send request to the API try { $response = $request->send(); $status = $response->getStatus(); $body = @json_decode($response->getBody(), true); if ($status == 200 && $body['status'] == 'OK') { $mimetypes = $body['result']; } else { throw new Exception($body['reason']); } } catch (Exception $e) { rcube::raise_error(array( 'code' => 500, 'type' => 'php', 'line' => __LINE__, 'file' => __FILE__, 'message' => $e->getMessage()), true, false); } return $mimetypes; } /** * Get list of available external storage drivers */ protected function get_external_storage_drivers() { // first get configured sources from Chwala $token = $this->get_api_token(); $request = $this->get_request(array('method' => 'folder_types'), $token); // send request to the API try { $response = $request->send(); $status = $response->getStatus(); $body = @json_decode($response->getBody(), true); if ($status == 200 && $body['status'] == 'OK') { $sources = $body['result']; } else { throw new Exception($body['reason']); } } catch (Exception $e) { rcube::raise_error($e, true, false); return; } $this->rc->output->set_env('external_sources', $sources); } /** * Registers translation labels for folder lists in UI */ protected function folder_list_env() { // folder list and actions $this->plugin->add_label( 'folderdeleting', 'folderdeleteconfirm', 'folderdeletenotice', 'collection_audio', 'collection_video', 'collection_image', 'collection_document', 'additionalfolders', 'listpermanent' ); $this->rc->output->add_label('foldersubscribing', 'foldersubscribed', 'folderunsubscribing', 'folderunsubscribed', 'searching' ); $this->rc->output->set_env('files_caps', $_SESSION['kolab_files_caps']); } } diff --git a/plugins/kolab_files/localization/en_US.inc b/plugins/kolab_files/localization/en_US.inc index e9eeeee7..b8eb0ac7 100644 --- a/plugins/kolab_files/localization/en_US.inc +++ b/plugins/kolab_files/localization/en_US.inc @@ -1,115 +1,129 @@ <?php /** * Localizations for the Kolab Files plugin * * Copyright (C) 2014, Kolab Systems AG * * For translation see https://www.transifex.com/projects/p/kolab/resource/kolab_files/ */ $labels['files'] = 'Files'; $labels['filepreview'] = 'File preview'; $labels['saveall'] = 'Save all to cloud...'; $labels['saveto'] = 'Save to cloud...'; $labels['saveas'] = 'Save as:'; $labels['create'] = 'Create'; $labels['save'] = 'Save'; $labels['cancel'] = 'Cancel'; $labels['fromcloud'] = 'From cloud...'; $labels['selectfiles'] = 'Select file(s) to attach...'; $labels['attachsel'] = 'Attach selected'; $labels['foldercreate'] = 'Create folder'; $labels['folderedit'] = 'Edit folder'; $labels['foldermount'] = 'Add storage'; $labels['folderdelete'] = 'Delete folder'; $labels['folderinside'] = 'Insert inside'; $labels['foldername'] = 'Folder name'; $labels['name'] = 'Name'; $labels['mtime'] = 'Modified'; $labels['type'] = 'Type'; $labels['upload'] = 'Upload'; $labels['uploadfile'] = 'Upload file(s)'; $labels['get'] = 'Download'; $labels['getfile'] = 'Download file'; $labels['view'] = 'View'; $labels['viewfile'] = 'View file'; $labels['rename'] = 'Rename'; $labels['deletefile'] = 'Delete file(s)'; $labels['edit'] = 'Edit'; $labels['editfile'] = 'Edit file'; $labels['save'] = 'Save'; $labels['savefile'] = 'Save file'; $labels['printfile'] = 'Print file'; $labels['renamefile'] = 'Rename a file'; $labels['createfile'] = 'Create a file'; $labels['createandedit'] = 'Create and Edit'; +$labels['copyfile'] = 'Copy a file'; +$labels['copyandedit'] = 'Copy and Edit'; $labels['documenttitle'] = 'Title:'; $labels['collection_audio'] = 'Audio'; $labels['collection_video'] = 'Video'; $labels['collection_image'] = 'Images'; $labels['collection_document'] = 'Documents'; $labels['uploading'] = 'Uploading file(s)...'; $labels['attaching'] = 'Attaching file(s)...'; $labels['authenticating'] = 'Authenticating...'; $labels['foldercreating'] = 'Creating folder...'; $labels['folderupdating'] = 'Updating folder...'; $labels['foldermounting'] = 'Adding external storage...'; $labels['folderdeleting'] = 'Deleting folder...'; $labels['folderdeleteconfirm'] = 'Are you sure you want to delete selected folder?'; $labels['folderdeletenotice'] = 'Folder deleted successfully.'; $labels['folderupdatenotice'] = 'Folder updated successfully.'; $labels['foldercreatenotice'] = 'Folder created successfully.'; $labels['foldermountnotice'] = 'Storage added successfully.'; $labels['folderauthtitle'] = 'Logon to $title'; $labels['saveallnotice'] = 'Successfully saved $n file(s).'; $labels['saveallerror'] = 'Saving $n file(s) failed.'; $labels['attacherror'] = 'Failed to attach file(s) from the cloud'; $labels['fileupdating'] = 'Updating file...'; $labels['filecreating'] = 'Creating file...'; $labels['filemoving'] = 'Moving file(s)...'; $labels['filecopying'] = 'Copying file(s)...'; $labels['filedeleting'] = 'Deleting file(s)...'; $labels['filedeleteconfirm'] = 'Are you sure you want to delete selected files?'; $labels['filedeletenotice'] = 'File(s) deleted successfully.'; $labels['filemovenotice'] = 'File(s) moved successfully.'; $labels['filecopynotice'] = 'File(s) copied successfully.'; $labels['uploadsizeerror'] = 'Maximum file size ($size) exceeded!'; $labels['listpermanent'] = 'List permanently'; $labels['additionalfolders'] = 'Additional folders'; $labels['allfolders'] = 'search in all folders'; $labels['fileskip'] = 'Skip'; $labels['fileskipall'] = 'Skip all'; $labels['fileoverwrite'] = 'Overwrite'; $labels['fileoverwriteall'] = 'Overwrite all'; $labels['filemoveconfirm'] = 'This action is going to overwrite the destination file: <b>$file</b>.'; +$labels['editfiledialog'] = 'Confirm editing action'; +$labels['editfilesessions'] = 'There are ongoing sessions on the selected file. Please, select the action you want to take.'; +$labels['newsession'] = 'Create a new session'; +$labels['newsessionro'] = 'Create a new session (copy the file into read-write location)'; +$labels['ownedsession'] = 'Continue your existing session'; +$labels['invitedsession'] = 'Join the session of $user'; +$labels['joinsession'] = 'Request an invitation from $user'; +$labels['editfilero'] = 'This file is read-only. Do you want to create and edit a copy of the file?'; +$labels['editfilerotitle'] = 'Read-only file'; +$labels['select'] = 'Select'; + $labels['storepasswords'] = 'remember password'; $labels['storepasswordsdesc'] = 'Stored passwords will be encrypted. Enable this if you do not want to be asked for the password on every login or you want this storage to be available via WebDAV.'; $labels['arialabelsearchform'] = 'Files search form'; $labels['arialabelquicksearchbox'] = 'Search input'; $labels['arialabellistoptions'] = 'Files list options'; $labels['arialabelfolderoptions'] = 'Folder actions'; $labels['arialabelfileeditform'] = 'File editing form'; $labels['arialabelfilecreateform'] = 'File creation form'; $labels['arialabelfilelist'] = 'List of files'; $labels['arialabelfoldercreateform'] = 'Folder creation form'; $labels['arialabelfoldereditform'] = 'Folder editing form'; $labels['arialabelfoldermountform'] = 'External storage form'; $labels['arialabelfolderauthform'] = 'External storage authentication form'; $labels['arialabelfolderlist'] = 'Folder/Collection selection'; $labels['arialabelfileselectdialog'] = 'File selection dialog'; $labels['arialabelattachmentoptions'] = 'Attachment save options'; $labels['arialabelfilesavedialog'] = 'File(s) saving dialog'; $labels['arialabelfileprops'] = 'File properties'; $labels['arialabelfilecontent'] = 'File content'; +$labels['arialabelfileeditdialog'] = 'File editing dialog'; $labels['type.plain'] = 'Plain Text Document'; $labels['type.vndoasisopendocumenttext'] = 'Text Document (ODF)'; $labels['type.html'] = 'HTML Document'; diff --git a/plugins/kolab_files/skins/larry/style.css b/plugins/kolab_files/skins/larry/style.css index 7963e7bb..dd61b9f5 100644 --- a/plugins/kolab_files/skins/larry/style.css +++ b/plugins/kolab_files/skins/larry/style.css @@ -1,538 +1,544 @@ /* Taskbar button */ #taskbar a.button-files span.button-inner { background: url(images/buttons.png) 0 0 no-repeat; height: 22px; } #taskbar a.button-files:hover span.button-inner, #taskbar a.button-files.button-selected span.button-inner { background: url(images/buttons.png) 0 -26px no-repeat; height: 22px; } /* Attachment toolbar button */ .toolbar a.button.saveas { background: url(images/buttons.png) center -277px no-repeat; } /* Files main interface */ #filestoolbar { position: absolute; height: 40px; left: 0; top: -6px; z-index: 10; } #filestoolbar a.button { background-image: url(images/buttons.png); } #filestoolbar a.button.upload { background-position: center -52px; } #filestoolbar a.button.get { background-position: center -93px; } #filestoolbar a.button.export { background-position: 0 -93px; min-width: 50px; } #filestoolbar a.button.open { background-position: center -131px; } #filestoolbar a.button.delete { background-image: url(../../../../skins/larry/images/buttons.png); } #filestoolbar a.button.create { background-position: center -315px; } #filestoolbar a.button.edit { background-position: center -173px; } #filestoolbar a.button.rename { background-position: center -357px; } #filestoolbar a.button.save { background-position: center -213px; } #filestoolbar a.button.print { background-image: url(../../../../skins/larry/images/buttons.png); background-position: center -810px; background-repeat: no-repeat; } #filestoolbar form { display: inline; } #filestoolbar > label { line-height: 42px; vertical-align: top; color: #555; text-shadow: 0 1px 1px #eee; } #document-title { width: 200px; } #collaborators { position: absolute; right: 0; top: -6px; } #collaborators #members img.photo { width: 32px; height: 32px; overflow: hidden; background: url(../../../../skins/larry/images/contactpic_32px.png) center center no-repeat #fff; border-radius: 5px; border: solid 3px #eee; margin-left: 5px; } #quicksearchbar #filesearchmenulink { position: absolute; top: 5px; left: 6px; } #folderlistbox, #fileinfobox { position: absolute; top: 42px; left: 0; width: 220px; bottom: 0; } #filelistcontainer, #filecontent { position: absolute; top: 42px; left: 232px; right: 0; bottom: 0; overflow: auto; } #filecontent.docedit { left: 0; } #filelistbox { overflow: auto; top: 0; left: 0; position: absolute; width: 100%; } #filecontent { overflow: hidden; } #filecontent iframe { width: 100%; height: 100%; margin: 0; border: 0; } #folderlistbox .scroller { top: 34px; } #folderlistbox .boxtitle a.iconbutton.search { background-position: -2px -317px; cursor: pointer; position: absolute; right: 8px; top: 8px; width: 16px; } .folderlist li.mailbox.collection a.name { background: url(images/folders.png) 5px 0 no-repeat; } .folderlist li.mailbox.collection.audio a.name { background-position: 5px -96px; } .folderlist li.mailbox.collection.video a.name { background-position: 5px -144px; } .folderlist li.mailbox.collection.document a.name { background-position: 5px 0; } .folderlist li.mailbox.collection.image a.name { background-position: 5px -48px; } .folderlist li.mailbox.collection.audio.selected a.name { background-position: 5px -119px; } .folderlist li.mailbox.collection.video.selected a.name { background-position: 5px -167px; } .folderlist li.mailbox.collection.document.selected a.name { backgroundposition: 5px -24px; } .folderlist li.mailbox.collection.image.selected a.name { background-position: 5px -72px; } #files-folder-list li a.subscription { display: inline-block; position: absolute; top: 5px; right: 5px; height: 16px; width: 16px; padding: 0; background: url(images/listicons.png) -16px -148px no-repeat; overflow: hidden; cursor: pointer; } #files-folder-list li a.subscription.subscribed { background-position: -16px -110px; } #files-folder-list .searchresults .boxtitle { border-radius: 0; padding: 2px 8px; } #files-folder-list .searchresults { margin-top: 8px; } #files-folder-list li.readonly { background-image: url(images/listicons.png); background-position: right -1000px; background-repeat: no-repeat; } #files-folder-list li.readonly { background-position: 88% -14px; } #files-folder-list li.readonly a { padding-right: 40px; } #files-folder-list li.readonly a.subscription { padding-right: 0; } .filelist thead tr th { padding: 0; } .filelist tbody tr td { padding: 2px 7px; height: 18px; line-height: 18px; } .filelist tr > .size { width: 80px; text-align: right; } .filelist thead tr > .size { text-align: left; } .filelist tr > .mtime { width: 125px; } .filelist tr > .options { width: 32px; } .filelist thead tr th.filename, .filelist tbody tr td.filename { width: 99%; white-space: nowrap; } .filelist thead tr th.sortedASC a, .filelist thead tr th.sortedDESC a { color: #004458; text-decoration: underline; background-image: url(../../../../skins/larry/images/listicons.png); background-position: right -912px; background-repeat: no-repeat; } .filelist thead tr th.sortedASC a { background-position: right -944px; } .filelist img { vertical-align: middle; display: inline-block; } .filelist tr > .options a.listmenu, .filelist tr > .flag span.flagged, .filelist tr > .flag span.unflagged, .filelist tr > .flag span.unflagged:hover { display: inline-block; vertical-align: middle; height: 18px; width: 20px; padding: 0; background: url(../../../../skins/larry/images/listicons.png) -100px 0 no-repeat; } .filelist thead tr th.options { padding: 0; border-left: none; } .filelist thead tr th.options .listmenu { background-position: 3px -970px; cursor: pointer; width: 24px; height: 20px; padding: 4px 4px 5px; text-indent: -5000px; } .filelist thead tr th.options .listmenu:focus { outline: none; background-color: rgba(73,180,210,0.7); } .filelist thead tr th:first-child { border-radius: 4px 0 0 0; } .filelist td.filename { padding: 0 4px; } .filelist tbody td.filename span { background: url(images/unknown.png) 0 0 no-repeat; padding: 1px 0 3px 20px; height: 16px; } .filelist tbody td.options span { background-image: url(images/listicons.png); display: block; height: 16px; } .filelist tbody tr.session > td.options span { background-position: center -212px; } .filelist tbody tr.session.invited > td.options span { background-position: center -236px; } .filelist tbody tr.session.owner > td.options span { background-position: center -188px; } #fileinfobox table { table-layout: fixed; } #fileinfobox table td.label { width: 60px; font-weight: bold; padding-right: 0; } #fileinfobox table td.data { overflow: hidden; text-overflow: ellipsis; } #fileinfobox table td.data.filename { font-weight: bold; } #fileinfobox table tr:first-child td { border-top: 0; } #filelistcontainer.droptarget.hover, #filelistcontainer.droptarget.active { border-color: #019bc6; box-shadow: 0 0 3px 2px rgba(71,135,177, 0.5); -webkit-box-shadow: 0 0 3px 2px rgba(71,135,177, 0.5); } #filelistcontainer.droptarget.hover { background-color: #d9ecf4; box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9); -webkit-box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9); } #filelistcontainer.droptarget.hover #filelist tbody td { background-color: #d9ecf4; } /* plugin dialogs */ #files-dialog, #files-compose-dialog, #files-file-rename-dialog, #files-file-create-dialog, +#files-file-edit-dialog, #files-folder-mount-dialog, #files-folder-auth-dialog, #files-folder-create-dialog, #files-folder-edit-dialog { display: none; } #files-compose-dialog #folderlistbox { right: auto; width: 190px; top: 45px; bottom: 5px; box-shadow: none; } #files-dialog #folderlistbox { top: 35px; bottom: 5px; left: 0; right: 0; width: auto; box-shadow: none; } #files-dialog #folderlistbox .scroller { top: 0; } #files-dialog .listsearchbox { display: block; position: absolute; top: 0; left: 0; right: 0; background-color: #fff; } #files-dialog.saveas #folderlistbox { bottom: 40px; } #files-compose-dialog #filelistcontainer { position: absolute; top: 45px; bottom: 5px; left: 200px; right: 0; box-shadow: none; } #files-compose-dialog #searchmenulink { width: 15px; } #files-compose-dialog .searchbox input { background-color: white; } #files-compose-dialog #quicksearchbar { top: 10px; right: 5px; } #files-compose-dialog #searchreset { cursor: pointer; } +#files-file-edit-dialog label { + display: block; + height: 20px; +} + a.filesaveall { display: inline-block; margin-top: .5em; padding: 3px 5px 4px 5px; } #file-save-as { height: 25px; white-space: nowrap; position: absolute; bottom: 8px; left: 5px; right: 5px; text-align: center; } #file-save-as input { width: 200px; } ul.toolbarmenu li span.saveas { background: url(images/buttons.png) -5px -253px no-repeat; } table.propform td.source.selected { background-color: #c7e3ef; } table.propform td.source div { display: inline-block; padding-left: 3px; vertical-align: middle; } table.propform td.source .name { font-weight: bold; } table.propform td.source .description { font-size: 9px; color: #666; } table.propform td.source img { vertical-align: middle; background-color: #e0e0e0; border-radius: 3px; margin: 3px; background-image: -moz-linear-gradient(center top, #888, #333); background-image: -webkit-linear-gradient(top, #888, #333); background-image: -ms-linear-gradient(top, #888, #333); } table.propform td.source table.propform td { border-bottom: 0; padding: 2px 10px; background-color: inherit; } .auth-options { margin-top: 5px; } .auth-options .description { color: #666; display: block; margin: 3px 0 0 25px; } .auth-options input, .auth-options label { vertical-align: middle; } diff --git a/plugins/kolab_files/skins/larry/templates/filepreview.html b/plugins/kolab_files/skins/larry/templates/filepreview.html index a0b51393..f669e32f 100644 --- a/plugins/kolab_files/skins/larry/templates/filepreview.html +++ b/plugins/kolab_files/skins/larry/templates/filepreview.html @@ -1,44 +1,54 @@ <roundcube:object name="doctype" value="html5" /> <html> <head> <title><roundcube:object name="pagetitle" /></title> <roundcube:include file="/includes/links.html" /> <script src="plugins/kolab_files/skins/larry/ui.js" type="text/javascript"></script> </head> <body class="files noscroll extwin"> <roundcube:include file="/includes/header.html" /> <div id="mainscreen"> <h1 class="voice"><roundcube:label name="kolab_files.filepreview" /></h1> <h2 id="aria-label-toolbar" class="voice"><roundcube:label name="arialabeltoolbar" /></h2> <div id="filestoolbar" class="toolbar" role="toolbar" aria-labelledby="aria-label-toolbar"> <roundcube:button command="files-get" type="link" class="button get disabled" classAct="button get" classSel="button get pressed" label="kolab_files.get" title="kolab_files.getfile" /> <roundcube:button command="files-edit" type="link" class="button edit disabled" classAct="button edit" classSel="button edit pressed" label="kolab_files.edit" title="kolab_files.editfile" /> <roundcube:button command="files-save" type="link" class="button save disabled" classAct="button save" classSel="button save pressed" label="kolab_files.save" title="kolab_files.savefile" style="display:none" /> <roundcube:button command="files-delete" type="link" class="button delete disabled" classAct="button delete" classSel="button delete pressed" label="delete" title="kolab_files.deletefile" /> <roundcube:button command="files-print" type="link" class="button print disabled" classAct="button print" classSel="button print pressed" label="print" title="kolab_files.printfile" /> </div> <h2 id="aria-label-fileprops" class="voice"><roundcube:label name="kolab_files.arialabelfileprops" /></h2> <div id="fileinfobox" class="uibox listbox" aria-labelledby="aria-label-toolbar"> <roundcube:object name="fileinfobox" id="fileinfo" class="listing" /> </div> <div id="filecontent" class="uibox"> <div class="iframebox"> <roundcube:object name="filepreviewframe" id="fileframe" frameborder="0" title="kolab_files.arialabelfilecontent" /> </div> </div> </div> +<div id="files-file-create-dialog" role="dialog" aria-labelledby="aria-label-filecreateform" aria-hidden="true"> + <h3 id="aria-label-filecreateform" class="voice"><roundcube:label name="kolab_files.arialabelfilecreateform" /></h3> + <roundcube:object name="file-create-form" /> +</div> +<div id="files-file-edit-dialog" role="dialog" aria-labelledby="aria-label-fileeditdialog" aria-hidden="true"> + <h3 id="aria-label-fileeditdialog" class="voice"><roundcube:label name="kolab_files.arialabelfileeditdialog" /></h3> + <roundcube:object name="file-edit-dialog"> +</div> + <roundcube:include file="/includes/footer.html" /> + <script type="text/javascript"> kolab_files_ui_init(); </script> </body> </html> diff --git a/plugins/kolab_files/skins/larry/templates/files.html b/plugins/kolab_files/skins/larry/templates/files.html index 0d1a6da2..6b8b8178 100644 --- a/plugins/kolab_files/skins/larry/templates/files.html +++ b/plugins/kolab_files/skins/larry/templates/files.html @@ -1,168 +1,172 @@ <roundcube:object name="doctype" value="html5" /> <html> <head> <title><roundcube:object name="pagetitle" /></title> <roundcube:include file="/includes/links.html" /> <script src="plugins/kolab_files/skins/larry/ui.js" type="text/javascript"></script> </head> <body class="files noscroll"> <roundcube:include file="/includes/header.html" /> <div id="mainscreen"> <h1 class="voice"><roundcube:label name="kolab_files.files" /></h1> <div id="filestoolbar" class="toolbar" role="toolbar" aria-labelledby="aria-label-toolbar"> <h2 id="aria-label-toolbar" class="voice"><roundcube:label name="arialabeltoolbar" /></h2> <form id="filesuploadform"> <roundcube:button command="files-upload" type="link" class="button upload disabled" classAct="button upload" classSel="button upload pressed" label="kolab_files.upload" title="kolab_files.uploadfile" /> </form> <roundcube:button command="files-get" type="link" class="button get disabled" classAct="button get" classSel="button get pressed" label="kolab_files.get" title="kolab_files.getfile" /> <roundcube:button command="files-open" type="link" class="button open disabled" classAct="button open" classSel="button open pressed" label="kolab_files.view" title="kolab_files.viewfile" /> <roundcube:button command="files-edit" type="link" class="button edit disabled" classAct="button edit" classSel="button edit pressed" label="kolab_files.edit" title="kolab_files.editfile" /> <roundcube:button command="files-create" type="link" class="button create disabled" classAct="button create" classSel="button create pressed" label="kolab_files.create" title="kolab_files.createfile" /> <roundcube:button command="files-rename" type="link" class="button rename disabled" classAct="button rename" classSel="button rename pressed" label="kolab_files.rename" title="kolab_files.renamefile" /> <roundcube:button command="files-delete" type="link" class="button delete disabled" classAct="button delete" classSel="button delete pressed" label="delete" title="kolab_files.deletefile" /> </div> <div id="quicksearchbar" class="quicksearchbox" role="search" aria-labelledby="aria-label-searchform"> <h2 id="aria-label-searchform" class="voice"><roundcube:label name="kolab_files.arialabelsearchform" /></h2> <label for="quicksearchbox" class="voice"><roundcube:label name="arialabelquicksearchbox" /></label> <roundcube:button name="filesearchmenulink" id="filesearchmenulink" class="iconbutton searchoptions" onclick="UI.toggle_popup('filesearchmenu', event); return false" title="searchmod" label="options" aria-haspopup="true" aria-expanded="false" aria-owns="filesearchmenu-menu" /> <roundcube:object name="file-search-form" id="quicksearchbox" /> <roundcube:button command="files-search-reset" id="searchreset" class="iconbutton reset" title="resetsearch" label="resetsearch" /> </div> <div id="folderlistbox" class="uibox listbox" role="navigation" aria-labelledby="aria-label-folderlist"> <h2 id="aria-label-folderlist" class="voice"><roundcube:label name="kolab_files.arialabelfolderlist" /></h2> <h2 id="folderslist-header" class="boxtitle"> <roundcube:label name="folders" /> <a href="#folders" class="iconbutton search" title="<roundcube:label name='findfolders' />" tabindex="0"><roundcube:label name='findfolders' /></a> </h2> <div class="listsearchbox"> <div class="searchbox" role="search" aria-labelledby="aria-label-foldersearchform"> <h3 id="aria-label-foldersearchform" class="voice"><roundcube:label name="arialabelfoldersearchform" /></h3> <label for="foldersearch" class="voice"><roundcube:label name="arialabelsearchterms" /></label> <input type="text" name="q" id="foldersearch" placeholder="<roundcube:label name='findfolders' />" /> <a class="iconbutton searchicon"></a> <roundcube:button command="reset-foldersearch" id="folderlistsearch-reset" class="iconbutton reset" title="resetsearch" label="resetsearch" /> </div> </div> <div id="files-folder-list" class="scroller withfooter"></div> <div id="folderlist-footer" class="boxfooter"> <roundcube:button command="folder-create" type="link" title="kolab_files.foldercreate" class="listbutton add" classAct="listbutton add" innerClass="inner" content="+" /><roundcube:button name="folderoptions" id="folderoptionslink" type="link" title="moreactions" class="listbutton groupactions" onclick="return UI.toggle_popup('folderoptions', event)" innerClass="inner" content="⚙" aria-haspopup="true" aria-expanded="false" aria-owns="folderoptionsmenu" /> <roundcube:if condition="env:files_quota" /> <span class="voice"><roundcube:label name="quota"></span> <roundcube:object name="filequotadisplay" id="quotadisplay" class="countdisplay" display="text" /> <roundcube:endif /> </div> </div> <div id="filelistcontainer" class="uibox droptarget"> <h2 id="aria-label-filelist" class="voice"><roundcube:label name="kolab_files.arialabelfilelist" /></h2> <div id="filelistbox" class="boxlistcontent"> <roundcube:object name="filelist" id="filelist" class="records-table filelist sortheader fixedheader" optionsmenuIcon="true" aria-labelledby="aria-label-filelist" /> </div> </div> </div> <div id="folderoptions" class="popupmenu" data-editable="true" aria-hidden="true"> <h3 id="aria-label-folderoptions" class="voice"><roundcube:label name="kolab_files.folderoptions" /></h3> <ul id="folderoptionsmenu" class="toolbarmenu" role="menu" aria-labelledby="aria-label-folderoptions"> <li role="menuitem"><roundcube:button command="folder-rename" label="rename" classAct="active" /></li> <li role="menuitem"><roundcube:button command="files-folder-delete" label="delete" classAct="active" /></li> <roundcube:if condition="!empty(env:external_sources)" /> <li role="menuitem"><roundcube:button command="folder-mount" label="kolab_files.foldermount" classAct="active" /></li> <roundcube:endif /> <li role="menuitem"><roundcube:button command="folders" task="settings" type="link" label="managefolders" classAct="active" /></li> <roundcube:container name="filesfolderoptions" id="folderoptionsmenu" /> </ul> </div> <div id="files-folder-create-dialog" role="dialog" aria-labelledby="aria-label-foldercreateform" aria-hidden="true"> <h3 id="aria-label-foldercreateform" class="voice"><roundcube:label name="kolab_files.arialabelfoldercreateform" /></h3> <roundcube:object name="folder-create-form" /> </div> <div id="files-folder-edit-dialog" role="dialog" aria-labelledby="aria-label-foldereditform" aria-hidden="true"> <h3 id="aria-label-foldereditform" class="voice"><roundcube:label name="kolab_files.arialabelfoldereditform" /></h3> <roundcube:object name="folder-edit-form" /> </div> <div id="files-folder-mount-dialog" role="dialog" aria-labelledby="aria-label-foldermountform" aria-hidden="true"> <h3 id="aria-label-foldermountform" class="voice"><roundcube:label name="kolab_files.arialabelfoldermountform" /></h3> <roundcube:object name="folder-mount-form" /> </div> <div id="files-file-rename-dialog" role="dialog" aria-labelledby="aria-label-filerenameform" aria-hidden="true"> <h3 id="aria-label-filerenameform" class="voice"><roundcube:label name="kolab_files.arialabelfilerenameform" /></h3> <roundcube:object name="file-rename-form" /> </div> <div id="files-file-create-dialog" role="dialog" aria-labelledby="aria-label-filecreateform" aria-hidden="true"> <h3 id="aria-label-filecreateform" class="voice"><roundcube:label name="kolab_files.arialabelfilecreateform" /></h3> <roundcube:object name="file-create-form" /> </div> <div id="files-folder-auth-dialog" role="dialog" aria-labelledby="aria-label-folderauthform" aria-hidden="true"> <h3 id="aria-label-folderauthform" class="voice"><roundcube:label name="kolab_files.arialabelfolderauthform" /></h3> <roundcube:object name="folder-auth-options"> </div> +<div id="files-file-edit-dialog" role="dialog" aria-labelledby="aria-label-fileeditdialog" aria-hidden="true"> + <h3 id="aria-label-fileeditdialog" class="voice"><roundcube:label name="kolab_files.arialabelfileeditdialog" /></h3> + <roundcube:object name="file-edit-dialog"> +</div> <div id="listoptions" class="propform popupdialog" data-editable="true" role="dialog" aria-labelledby="aria-label-listoptions" aria-hidden="true"> <h3 id="aria-label-listoptions" class="voice"><roundcube:label name="kolab_files.arialabellistoptions" /></h3> <roundcube:if condition="!in_array('kolab_files_list_cols', (array)config:dont_override)" /> <fieldset class="floating"> <legend><roundcube:label name="listcolumns" /></legend> <ul class="proplist"> <li><label class="disabled"><input type="checkbox" name="list_col[]" value="options" checked="checked" disabled="disabled" /> <span><roundcube:label name="options" /></span></label></li> <li><label class="disabled"><input type="checkbox" name="list_col[]" value="name" checked="checked" disabled="disabled" /> <span><roundcube:label name="kolab_files.name" /></span></label></li> <li><label><input type="checkbox" name="list_col[]" value="mtime" /> <span><roundcube:label name="kolab_files.mtime" /></span></label></li> <li><label><input type="checkbox" name="list_col[]" value="size" /> <span><roundcube:label name="size" /></span></label></li> </ul> </fieldset> <roundcube:endif /> <roundcube:if condition="!in_array('kolab_files_sort_col', (array)config:dont_override)" /> <fieldset class="floating"> <legend><roundcube:label name="listsorting" /></legend> <ul class="proplist"> <li><label><input type="radio" name="sort_col" value="name" /> <span><roundcube:label name="kolab_files.name" /></span></label></li> <li><label><input type="radio" name="sort_col" value="mtime" /> <span><roundcube:label name="kolab_files.mtime" /></span></label></li> <li><label><input type="radio" name="sort_col" value="size" /> <span><roundcube:label name="size" /></span></label></li> </ul> </fieldset> <roundcube:endif /> <roundcube:if condition="!in_array('kolab_files_sort_order', (array)config:dont_override)" /> <fieldset class="floating"> <legend><roundcube:label name="listorder" /></legend> <ul class="proplist"> <li><label><input type="radio" name="sort_ord" value="ASC" /> <span><roundcube:label name="asc" /></span></label></li> <li><label><input type="radio" name="sort_ord" value="DESC" /> <span><roundcube:label name="desc" /></span></label></li> </ul> </fieldset> <roundcube:endif /> <br style="clear:both" /> <div class="formbuttons"> <roundcube:button command="menu-save" id="listmenusave" type="input" class="button mainaction" label="save" /> <roundcube:button command="menu-close" prop="filelistmenu" id="listmenucancel" type="input" class="button" label="cancel" /> </div> </div> <div id="dragfilemenu" class="popupmenu" aria-hidden="true"> <ul class="toolbarmenu" role="menu"> <li role="menuitem"><roundcube:button command="files-move" onclick="return kolab_files_drag_menu_action('files-move')" label="move" classAct="active" /></li> <li role="menuitem"><roundcube:button command="files-copy" onclick="return kolab_files_drag_menu_action('files-copy')" label="copy" classAct="active" /></li> </ul> </div> <div id="filesearchmenu" class="popupmenu" data-editable="true" aria-hidden="true"> <h3 id="aria-label-searchmenu" class="voice"><roundcube:label name="searchmod" /></h3> <ul id="filesearchmenu-menu" class="toolbarmenu" role="menu" aria-labelledby="aria-label-searchmenu"> <li role="menuitem"><label><input type="checkbox" name="all_folders" value="1" id="search_all_folders" /> <span><roundcube:label name="kolab_files.allfolders" /></span></label></li> </ul> </div> <roundcube:include file="/includes/footer.html" /> <script type="text/javascript"> kolab_files_ui_init(); </script> </body> </html>