diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php index b9eba2de..fec96f49 100644 --- a/plugins/calendar/drivers/kolab/kolab_driver.php +++ b/plugins/calendar/drivers/kolab/kolab_driver.php @@ -93,10 +93,13 @@ class kolab_driver extends calendar_driver $this->calendars = array(); foreach ($folders as $folder) { - if ($folder instanceof kolab_storage_folder_user) + if ($folder instanceof kolab_storage_folder_user) { $calendar = new kolab_user_calendar($folder->name, $this->cal); - else + $calendar->subscriptions = count($folder->children) > 0; + } + else { $calendar = new kolab_calendar($folder->name, $this->cal); + } if ($calendar->ready) { $this->calendars[$calendar->id] = $calendar; @@ -210,7 +213,7 @@ class kolab_driver extends calendar_driver } if ($cal->subscriptions) { - $calendars[$cal->id]['subscribed'] = (bool)$cal->is_subscribed(); + $calendars[$cal->id]['subscribed'] = $cal->is_subscribed(); } } @@ -488,6 +491,7 @@ class kolab_driver extends calendar_driver foreach (kolab_storage::list_user_folders($user, 'event', false) as $foldername) { $cal = new kolab_calendar($foldername, $this->cal); $this->calendars[$cal->id] = $cal; + $calendar->subscriptions = true; } } diff --git a/plugins/calendar/drivers/kolab/kolab_user_calendar.php b/plugins/calendar/drivers/kolab/kolab_user_calendar.php index 859dc4c8..18b1eccf 100644 --- a/plugins/calendar/drivers/kolab/kolab_user_calendar.php +++ b/plugins/calendar/drivers/kolab/kolab_user_calendar.php @@ -51,6 +51,7 @@ class kolab_user_calendar extends kolab_calendar } $this->ready = !empty($this->userdata['kolabtargetfolder']); + $this->storage->type = 'event'; if ($this->ready) { // ID is derrived from the user's kolabtargetfolder attribute @@ -141,6 +142,15 @@ class kolab_user_calendar extends kolab_calendar return false; } + /** + * Check subscription status of this folder + * + * @return boolean True if subscribed, false if not + */ + public function is_subscribed() + { + return $this->storage->is_subscribed(); + } /** * Update properties of this calendar folder @@ -201,7 +211,7 @@ class kolab_user_calendar extends kolab_calendar // aggregate all calendar folders the user shares (but are not subscribed) foreach (kolab_storage::list_user_folders($this->userdata, 'event', false) as $foldername) { - if (!kolab_storage::folder_is_subscribed($foldername, true)) { + if (!empty($_REQUEST['_quickview']) || !kolab_storage::folder_is_subscribed($foldername, true)) { $cal = new kolab_calendar($foldername, $this->cal); foreach ($cal->list_events($start, $end, $search, 1) as $event) { $this->events[$event['id']] = $event; @@ -302,7 +312,7 @@ class kolab_user_calendar extends kolab_calendar 'id' => md5($this->id . $from->format('U') . '/' . $to->format('U')), 'calendar' => $this->id, 'changed' => $fb['created'] ?: new DateTime(), - 'title' => $titlemap[$type] ?: $type, + 'title' => $this->get_name() . ' ' . ($titlemap[$type] ?: $type), 'start' => $from, 'end' => $to, 'free_busy' => $statusmap[$type] ?: 'busy', diff --git a/plugins/calendar/lib/calendar_ui.php b/plugins/calendar/lib/calendar_ui.php index e66e5f85..26936423 100644 --- a/plugins/calendar/lib/calendar_ui.php +++ b/plugins/calendar/lib/calendar_ui.php @@ -298,6 +298,8 @@ class calendar_ui $classes[] = 'readonly'; if ($prop['subscribed']) $classes[] = 'subscribed'; + if ($prop['subscribed'] === 2) + $classes[] = 'partial'; if ($prop['class']) $classes[] = $prop['class']; diff --git a/plugins/calendar/skins/larry/calendar.css b/plugins/calendar/skins/larry/calendar.css index 17efff5c..c6a17c14 100644 --- a/plugins/calendar/skins/larry/calendar.css +++ b/plugins/calendar/skins/larry/calendar.css @@ -289,6 +289,11 @@ pre { background-position: -16px -110px; } +#calendars .treelist div.subscribed.partial a.subscribed, +#calendars .treelist div.subscribed.partial a.subscribed:focus { + background-position: -16px -148px; +} + #calendars .treelist li a.quickview { display: inline-block; position: absolute; diff --git a/plugins/calendar/skins/larry/images/calendars.png b/plugins/calendar/skins/larry/images/calendars.png index 5e53cb63..88eba63f 100644 Binary files a/plugins/calendar/skins/larry/images/calendars.png and b/plugins/calendar/skins/larry/images/calendars.png differ diff --git a/plugins/libkolab/js/folderlist.js b/plugins/libkolab/js/folderlist.js index 1c8ce2f6..b5839477 100644 --- a/plugins/libkolab/js/folderlist.js +++ b/plugins/libkolab/js/folderlist.js @@ -149,7 +149,8 @@ function kolab_folderlist(node, p) prop = search_results[id], parent_id = prop.parent || null, has_children = node.children && node.children.length, - dom_node = has_children ? li.children().first().clone(true, true) : li.children().first(); + dom_node = has_children ? li.children().first().clone(true, true) : li.children().first(), + childs = []; // find parent node and insert at the right place if (parent_id && me.get_node(parent_id)) { @@ -171,18 +172,58 @@ function kolab_folderlist(node, p) .removeClass('virtual'); } else { + // copy childs, too + if (has_children && prop.group == 'other user') { + for (var cid, j=0; j < node.children.length; j++) { + if ((cid = node.children[j].id) && search_results[cid]) { + childs.push(search_results_widget.get_node(cid)); + } + } + } + // move this result item to the main list widget me.insert({ id: id, classes: [ prop.group || '' ], virtual: prop.virtual, html: dom_node, + level: node.level, + collapsed: true, + children: childs }, parent_id, prop.group); } delete prop.html; prop.active = active; me.triggerEvent('insert-item', { id: id, data: prop, item: li }); + + // register childs, too + if (childs.length) { + for (var cid, j=0; j < node.children.length; j++) { + if ((cid = node.children[j].id) && search_results[cid]) { + prop = search_results[cid]; + delete prop.html; + prop.active = false; + me.triggerEvent('insert-item', { id: cid, data: prop }); + } + } + } + } + + // update the given item's parent's (partial) subscription state + function parent_subscription_status(li) + { + var top_li = li.closest(me.container.children('li')), + all_childs = $('li > div:not(.treetoggle)', top_li), + subscribed = all_childs.filter('.subscribed').length; + + if (subscribed == 0) { + top_li.children('div:first').removeClass('subscribed partial'); + } + else { + top_li.children('div:first') + .addClass('subscribed')[subscribed < all_childs.length ? 'addClass' : 'removeClass']('partial'); + } } // do some magic when search is performed on the widget @@ -242,21 +283,39 @@ function kolab_folderlist(node, p) this.container.on('click', 'a.subscribed, span.subscribed', function(e){ var li = $(this).closest('li'), id = li.attr('id').replace(new RegExp('^'+p.id_prefix), ''), - div = li.children().first(); + div = li.children().first(), + is_subscribed; - if (me.is_search()) + if (me.is_search()) { id = id.replace(/--xsR$/, ''); + li = $(me.get_item(id, true)); + div = $(div).add(li.children().first()); + } if (p.id_decode) id = p.id_decode(id); div.toggleClass('subscribed'); - $(this).attr('aria-checked', div.hasClass('subscribed') ? 'true' : 'false'); - me.triggerEvent('subscribe', { id: id, subscribed: div.hasClass('subscribed'), item: li }); + is_subscribed = div.hasClass('subscribed'); + $(this).attr('aria-checked', is_subscribed ? 'true' : 'false'); + me.triggerEvent('subscribe', { id: id, subscribed: is_subscribed, item: li }); + + // update subscribe state of all 'virtual user' child folders + if (li.hasClass('other user')) { + $('ul li > div', li).each(function() { + $(this)[is_subscribed ? 'addClass' : 'removeClass']('subscribed'); + $('.subscribed', div).attr('aria-checked', is_subscribed ? 'true' : 'false'); + }); + div.removeClass('partial'); + } + // propagate subscription state to parent 'virtual user' folder + else if (li.closest('li.other.user').length) { + parent_subscription_status(li); + } e.stopPropagation(); return false; - }) + }); } diff --git a/plugins/libkolab/lib/kolab_storage.php b/plugins/libkolab/lib/kolab_storage.php index 144655c4..4c29a201 100644 --- a/plugins/libkolab/lib/kolab_storage.php +++ b/plugins/libkolab/lib/kolab_storage.php @@ -1534,6 +1534,16 @@ class kolab_storage $folders[$foldername] = new kolab_storage_folder_user($foldername, $other_ns); } } + + // for every (subscribed) user folder, list all (unsubscribed) subfolders + foreach ($folders as $userfolder) { + foreach ((array)self::list_folders($userfolder->name . $delimiter, '*', $type, false, $folderdata) as $foldername) { + if (!$folders[$foldername]) { + $folders[$foldername] = new kolab_storage_folder($foldername, $folderdata[$foldername]); + $userfolder->children[] = $folders[$foldername]; + } + } + } } return $folders; diff --git a/plugins/libkolab/lib/kolab_storage_folder_api.php b/plugins/libkolab/lib/kolab_storage_folder_api.php index ef3309e0..9e64f4eb 100644 --- a/plugins/libkolab/lib/kolab_storage_folder_api.php +++ b/plugins/libkolab/lib/kolab_storage_folder_api.php @@ -326,5 +326,14 @@ abstract class kolab_storage_folder_api return $subscribed ? kolab_storage::folder_subscribe($this->name) : kolab_storage::folder_unsubscribe($this->name); } + /** + * Return folder name as string representation of this object + * + * @return string Full IMAP folder name + */ + public function __toString() + { + return $this->name; + } } diff --git a/plugins/libkolab/lib/kolab_storage_folder_user.php b/plugins/libkolab/lib/kolab_storage_folder_user.php index 1c37da9e..7c141c50 100644 --- a/plugins/libkolab/lib/kolab_storage_folder_user.php +++ b/plugins/libkolab/lib/kolab_storage_folder_user.php @@ -26,6 +26,7 @@ class kolab_storage_folder_user extends kolab_storage_folder_virtual protected static $ldapcache = array(); public $ldaprec; + public $type; /** * Default constructor @@ -85,13 +86,28 @@ class kolab_storage_folder_user extends kolab_storage_folder_virtual } /** - * Check subscription status of this folder + * Check subscription status of this folder. + * Subscription of a virtual user folder depends on the subscriptions of subfolders. * * @return boolean True if subscribed, false if not */ public function is_subscribed() { - return kolab_storage::folder_is_subscribed($this->name, true); + if (!empty($this->type)) { + $children = $subscribed = 0; + $delimiter = $this->imap->get_hierarchy_delimiter(); + foreach ((array)kolab_storage::list_folders($this->name . $delimiter, '*', $this->type, false) as $subfolder) { + if (kolab_storage::folder_is_subscribed($subfolder)) { + $subscribed++; + } + $children++; + } + if ($subscribed > 0) { + return $subscribed == $children ? true : 2; + } + } + + return false; } /** @@ -103,9 +119,17 @@ class kolab_storage_folder_user extends kolab_storage_folder_virtual */ public function subscribe($subscribed) { - return $subscribed ? - kolab_storage::folder_subscribe($this->name, true) : - kolab_storage::folder_unsubscribe($this->name, true); + $success = false; + + // (un)subscribe all subfolders of a given type + if (!empty($this->type)) { + $delimiter = $this->imap->get_hierarchy_delimiter(); + foreach ((array)kolab_storage::list_folders($this->name . $delimiter, '*', $this->type, false) as $subfolder) { + $success |= ($subscribed ? kolab_storage::folder_subscribe($subfolder) : kolab_storage::folder_unsubscribe($subfolder)); + } + } + + return $success; } } \ No newline at end of file