diff --git a/plugins/kolab_files/kolab_files.js b/plugins/kolab_files/kolab_files.js index 1b2fc6cb..e7bd0662 100644 --- a/plugins/kolab_files/kolab_files.js +++ b/plugins/kolab_files/kolab_files.js @@ -69,8 +69,8 @@ window.rcmail && rcmail.addEventListener('init', function() { /* rcmail.file_list.addEventListener('dragstart', function(o){ p.drag_start(o); }); rcmail.file_list.addEventListener('dragmove', function(e){ p.drag_move(e); }); - rcmail.file_list.addEventListener('dragend', function(e){ p.drag_end(e); }); */ + rcmail.file_list.addEventListener('dragend', function(e){ kolab_files_drag_end(e); }); rcmail.file_list.addEventListener('column_replace', function(e){ kolab_files_set_coltypes(e); }); rcmail.file_list.addEventListener('listupdate', function(e){ rcmail.triggerEvent('listupdate', e); }); @@ -356,6 +356,17 @@ kolab_files_list_select = function(list) // rcmail.select_all_mode = false; }; +kolab_files_drag_end = function() +{ + var folder = $('#files-folder-list li.droptarget').removeClass('droptarget'); + + if (folder.length) { + folder = folder.data('folder'); + + file_api.file_move(kolab_files_selected(), folder); + } +}; + kolab_files_selected = function() { var files = []; @@ -369,6 +380,11 @@ kolab_files_selected = function() return files; }; + +/***********************************************************/ +/********** Commands **********/ +/***********************************************************/ + rcube_webmail.prototype.files_sort = function(props) { var params = {}, @@ -507,7 +523,7 @@ function kolab_files_ui() $.each(this.env.folders, function(i, f) { var row = $('
  • '); - row.attr('id', f.id) + row.attr('id', f.id).data('folder', i) .append($('').text(f.name)) .click(function() { file_api.folder_select(i); }); @@ -516,6 +532,15 @@ function kolab_files_ui() if (f.virtual) row.addClass('virtual'); + else + row.mouseenter(function() { + if (rcmail.file_list.drag_active) + $(this).addClass('droptarget'); + }) + .mouseleave(function() { + if (rcmail.file_list.drag_active) + $(this).removeClass('droptarget'); + }); list.append(row); @@ -545,6 +570,46 @@ function kolab_files_ui() this.file_list(); }; + // folder create request + this.folder_create = function(folder) + { + this.req = this.set_busy(true, 'kolab_files.foldercreating'); + this.get('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 delete request + this.folder_delete = function(folder) + { + this.req = this.set_busy(true, 'kolab_files.folderdeleting'); + this.get('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', 'files-folder-rename', false); + this.display_message('kolab_files.folderdeletenotice', 'confirmation'); + + // refresh folders list + this.folder_list(); + }; + this.file_list = function(params) { if (!this.env.folder || !rcmail.gui_objects.filelist) @@ -622,46 +687,6 @@ function kolab_files_ui() $(row).addClass('selected'); }; - // folder create request - this.folder_create = function(folder) - { - this.req = this.set_busy(true, 'kolab_files.foldercreating'); - this.get('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 delete request - this.folder_delete = function(folder) - { - this.req = this.set_busy(true, 'kolab_files.folderdeleting'); - this.get('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', 'files-folder-rename', false); - this.display_message('kolab_files.folderdeletenotice', 'confirmation'); - - // refresh folders list - this.folder_list(); - }; - this.file_search = function(value) { if (value) { @@ -708,6 +733,31 @@ function kolab_files_ui() this.file_list(); }; + // file(s) move request + this.file_move = function(files, folder) + { + if (!files || !files.length || !folder) + return; + + var list = {}; + $.each(files, function(i, v) { + list[v] = folder + file_api.env.directory_separator + file_api.file_name(v); + }); + + this.req = this.set_busy(true, 'kolab_files.filemoving'); + this.get('file_move', {file: list}, 'file_move_response'); + }; + + // file(s) move response handler + this.file_move_response = function(response) + { + if (!this.response(response)) + return; + + this.display_message('kolab_files.filemovenotice', 'confirmation'); + this.file_list(); + }; + // file upload request this.file_upload = function(form) { diff --git a/plugins/kolab_files/lib/kolab_files_engine.php b/plugins/kolab_files/lib/kolab_files_engine.php index 2e109abc..4a87080e 100644 --- a/plugins/kolab_files/lib/kolab_files_engine.php +++ b/plugins/kolab_files/lib/kolab_files_engine.php @@ -462,7 +462,8 @@ class kolab_files_engine $this->rc->output->add_label('deletefolderconfirm', 'kolab_files.folderdeleting', 'kolab_files.foldercreating', 'kolab_files.uploading', 'kolab_files.filedeleteconfirm', - 'kolab_files.folderdeleteconfirm', 'kolab_files.filedeleting'); + 'kolab_files.folderdeleteconfirm', 'kolab_files.filedeleting', 'kolab_files.filedeletenotice', + 'kolab_files.filemoving', 'kolab_files.filemovenotice'); $this->rc->output->set_pagetitle($this->plugin->gettext('files')); $this->rc->output->send('kolab_files.files'); diff --git a/plugins/kolab_files/localization/en_US.inc b/plugins/kolab_files/localization/en_US.inc index e453f5e6..f657cb56 100644 --- a/plugins/kolab_files/localization/en_US.inc +++ b/plugins/kolab_files/localization/en_US.inc @@ -33,8 +33,10 @@ $labels['foldercreatenotice'] = 'Folder created successfully.'; $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['filemoving'] = 'Moving 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.'; ?> diff --git a/plugins/kolab_files/skins/larry/style.css b/plugins/kolab_files/skins/larry/style.css index d4c86285..6a72335f 100644 --- a/plugins/kolab_files/skins/larry/style.css +++ b/plugins/kolab_files/skins/larry/style.css @@ -63,6 +63,15 @@ overflow: auto; } +#filelistbox { + bottom: 28px; + overflow: auto; + top: 0; + left: 0; + position: absolute; + width: 100%; +} + #files-folder-list ul li span.name { background: url(../../../../skins/larry/images/listicons.png) 6px 3px no-repeat; padding: 6px 8px 2px 32px; diff --git a/plugins/kolab_files/skins/larry/templates/files.html b/plugins/kolab_files/skins/larry/templates/files.html index c91f64bb..1f23e654 100644 --- a/plugins/kolab_files/skins/larry/templates/files.html +++ b/plugins/kolab_files/skins/larry/templates/files.html @@ -40,8 +40,10 @@ -
    - +
    +
    + +
    diff --git a/plugins/kolab_files/skins/larry/ui.js b/plugins/kolab_files/skins/larry/ui.js index a0a3c494..a5e359bd 100644 --- a/plugins/kolab_files/skins/larry/ui.js +++ b/plugins/kolab_files/skins/larry/ui.js @@ -103,12 +103,12 @@ function kolab_files_upload_input(button) file.attr({name: 'file[]', type: 'file', multiple: 'multiple', size: 5}) .change(function() { rcmail.files_upload('#filesuploadform'); }) // opacity:0 does the trick, display/visibility doesn't work - .css({opacity: 0, cursor: 'pointer', position: 'absolute', top: '10000px', left: '10000px'}); + .css({opacity: 0, cursor: 'pointer', outline: 'none', position: 'absolute', top: '10000px', left: '10000px'}); - // In FF we need to move the browser file-input's button under the cursor + // 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) - file.css({marginLeft: '-75px'}); + if (bw.mz || bw.ie) + file.css({marginLeft: '-80px'}); // Note: now, I observe problem with cursor style on FF < 4 only link.css({overflow: 'hidden', cursor: 'pointer'}) diff --git a/plugins/libkolab/lib/kolab_format_event.php b/plugins/libkolab/lib/kolab_format_event.php index 2b3f0c8f..ec97767c 100644 --- a/plugins/libkolab/lib/kolab_format_event.php +++ b/plugins/libkolab/lib/kolab_format_event.php @@ -192,7 +192,7 @@ class kolab_format_event extends kolab_format_xcal } // read exception event objects - if (($exceptions = $this->obj->exceptions()) && $exceptions->size()) { + if (($exceptions = $this->obj->exceptions()) && is_object($exceptions) && $exceptions->size()) { for ($i=0; $i < $exceptions->size(); $i++) { if (($exobj = $exceptions->get($i))) { $exception = new kolab_format_event($exobj); diff --git a/plugins/libkolab/lib/kolab_storage_folder.php b/plugins/libkolab/lib/kolab_storage_folder.php index 7e10f2e6..dd0e8d2d 100644 --- a/plugins/libkolab/lib/kolab_storage_folder.php +++ b/plugins/libkolab/lib/kolab_storage_folder.php @@ -548,9 +548,9 @@ class kolab_storage_folder // detect old Kolab 2.0 format if (strpos($xmlhead, '<' . $xmltype) !== false && strpos($xmlhead, 'xmlns=') === false) - $format_version = 2.0; + $format_version = '2.0'; else - $format_version = 3.0; // assume 3.0 + $format_version = '3.0'; // assume 3.0 } // get Kolab format handler for the given type @@ -588,7 +588,6 @@ class kolab_storage_folder return false; } - /** * Save an object in this folder. * @@ -659,6 +658,11 @@ class kolab_storage_folder } } + // save recurrence exceptions as individual objects due to lack of support in Kolab v2 format + if (kolab_storage::$version == '2.0' && $object['recurrence']['EXCEPTIONS']) { + $this->save_recurrence_exceptions($object, $type); + } + // check IMAP BINARY extension support for 'file' objects // allow configuration to workaround bug in Cyrus < 2.4.17 $rcmail = rcube::get_instance(); @@ -666,6 +670,12 @@ class kolab_storage_folder // generate and save object message if ($raw_msg = $this->build_message($object, $type, $binary)) { + // resolve old msguid before saving + if ($uid && empty($object['_msguid']) && ($msguid = $this->cache->uid2msguid($uid))) { + $object['_msguid'] = $msguid; + $object['_mailbox'] = $this->name; + } + if (is_array($raw_msg)) { $result = $this->imap->save_message($this->name, $raw_msg[0], $raw_msg[1], true, null, null, $binary); @unlink($raw_msg[0]); @@ -679,10 +689,6 @@ class kolab_storage_folder $this->imap->delete_message($object['_msguid'], $object['_mailbox']); $this->cache->set($object['_msguid'], false, $object['_mailbox']); } - else if ($result && $uid && ($msguid = $this->cache->uid2msguid($uid))) { - $this->imap->delete_message($msguid, $this->name); - $this->cache->set($object['_msguid'], false); - } // update cache with new UID if ($result) { @@ -694,6 +700,68 @@ class kolab_storage_folder return $result; } + /** + * Save recurrence exceptions as individual objects. + * The Kolab v2 format doesn't allow us to save fully embedded exception objects. + * + * @param array Hash array with event properties + * @param string Object type + */ + private function save_recurrence_exceptions(&$object, $type = null) + { + if ($object['recurrence']['EXCEPTIONS']) { + $exdates = array(); + foreach ((array)$object['recurrence']['EXDATE'] as $exdate) { + $key = is_a($exdate, 'DateTime') ? $exdate->format('Y-m-d') : strval($exdate); + $exdates[$key] = 1; + } + + // save every exception as individual object + foreach((array)$object['recurrence']['EXCEPTIONS'] as $exception) { + $exception['uid'] = self::recurrence_exception_uid($object['uid'], $exception['start']->format('Ymd')); + $exception['sequence'] = $object['sequence'] + 1; + + if ($exception['thisandfuture']) { + $exception['recurrence'] = $object['recurrence']; + + // adjust the recurrence duration of the exception + if ($object['recurrence']['COUNT']) { + $recurrence = new kolab_date_recurrence($object['_formatobj']); + if ($end = $recurrence->end()) { + unset($exception['recurrence']['COUNT']); + $exception['recurrence']['UNTIL'] = new DateTime('@'.$end); + } + } + + // set UNTIL date if we have a thisandfuture exception + $untildate = clone $exception['start']; + $untildate->sub(new DateInterval('P1D')); + $object['recurrence']['UNTIL'] = $untildate; + unset($object['recurrence']['COUNT']); + } + else { + if (!$exdates[$exception['start']->format('Y-m-d')]) + $object['recurrence']['EXDATE'][] = clone $exception['start']; + unset($exception['recurrence']); + } + + unset($exception['recurrence']['EXCEPTIONS'], $exception['_formatobj'], $exception['_msguid']); + $this->save($exception, $type, $exception['uid']); + } + + unset($object['recurrence']['EXCEPTIONS']); + } + } + + /** + * Generate an object UID with the given recurrence-ID in a way that it is + * unique (the original UID is not a substring) but still recoverable. + */ + private static function recurrence_exception_uid($uid, $recurrence_id) + { + $offset = -2; + return substr($uid, 0, $offset) . '-' . $recurrence_id . '-' . substr($uid, $offset); + } /** * Delete the specified object from this folder.