diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php index a95e1c2f..4df2d85f 100644 --- a/plugins/calendar/drivers/kolab/kolab_driver.php +++ b/plugins/calendar/drivers/kolab/kolab_driver.php @@ -79,7 +79,7 @@ class kolab_driver extends calendar_driver return $this->calendars; // get all folders that have "event" type, sorted by namespace/name - $folders = kolab_storage::sort_folders(kolab_storage::get_folders('event') + kolab_storage::get_user_folders(true)); + $folders = kolab_storage::sort_folders(kolab_storage::get_folders('event') + kolab_storage::get_user_folders('event', true)); $this->calendars = array(); foreach ($folders as $folder) { diff --git a/plugins/libkolab/config.inc.php.dist b/plugins/libkolab/config.inc.php.dist index 3a3c2875..fd8ac843 100644 --- a/plugins/libkolab/config.inc.php.dist +++ b/plugins/libkolab/config.inc.php.dist @@ -16,6 +16,10 @@ $rcmail_config['kolab_freebusy_server'] = 'https:///'; // folders in calendar view or available addressbooks $rcmail_config['kolab_use_subscriptions'] = false; +// List any of 'personal','shared','other' namespaces to be excluded from groupware folder listing +// example: array('other'); +$rcmail_config['kolab_skip_namespace'] = null; + // Enables the use of displayname folder annotations as introduced in KEP:? // for displaying resource folder names (experimental!) $rcmail_config['kolab_custom_display_names'] = false; diff --git a/plugins/libkolab/lib/kolab_storage.php b/plugins/libkolab/lib/kolab_storage.php index 4266fb5e..1fd3d584 100644 --- a/plugins/libkolab/lib/kolab_storage.php +++ b/plugins/libkolab/lib/kolab_storage.php @@ -40,7 +40,9 @@ class kolab_storage public static $encode_ids = false; private static $ready = false; + private static $with_tempsubs = true; private static $subscriptions; + private static $typedata = array(); private static $states; private static $config; private static $imap; @@ -313,7 +315,7 @@ class kolab_storage } } - return '/'; + return ''; } @@ -748,11 +750,12 @@ class kolab_storage if ($subscribed) { $folders = self::$imap->list_folders_subscribed($root, $mbox); // add temporarily subscribed folders - if (is_array($_SESSION['kolab_subscribed_folders'])) + if (self::$with_tempsubs && is_array($_SESSION['kolab_subscribed_folders'])) { $folders = array_unique(array_merge($folders, $_SESSION['kolab_subscribed_folders'])); + } } else { - $folders = self::$imap->list_folders($root, $mbox); + $folders = self::_imap_list_folders($root, $mbox); } return $folders; @@ -784,11 +787,11 @@ class kolab_storage $folders = self::$imap->list_folders_subscribed($root, $mbox); // add temporarily subscribed folders - if (is_array($_SESSION['kolab_subscribed_folders'])) + if (self::$with_tempsubs && is_array($_SESSION['kolab_subscribed_folders'])) $folders = array_unique(array_merge($folders, $_SESSION['kolab_subscribed_folders'])); } else { - $folders = self::$imap->list_folders($root, $mbox); + $folders = self::_imap_list_folders($root, $mbox); } // In case of an error, return empty list (?) @@ -798,6 +801,11 @@ class kolab_storage // Filter folders list foreach ($folders as $idx => $folder) { + // lookup folder type + if (!array_key_exists($folder, $folderdata)) { + $folderdata[$folder] = self::folder_type($folder); + } + $type = $folderdata[$folder]; if ($filter == 'mail' && empty($type)) { @@ -811,6 +819,38 @@ class kolab_storage return $folders; } + /** + * Wrapper for rcube_imap::list_folders() with optional post-filtering + */ + protected static function _imap_list_folders($root, $mbox) + { + $postfilter = null; + + // compose a post-filter expression for the excluded namespaces + if ($root . $mbox == '*' && ($skip_ns = self::$config->get('kolab_skip_namespace'))) { + $excludes = array(); + foreach ((array)$skip_ns as $ns) { + if ($ns_root = self::namespace_root($ns)) { + $excludes[] = $ns_root; + } + } + + if (count($excludes)) { + $postfilter = '!^(' . join(')|(', array_map('preg_quote', $excludes)) . ')!'; + } + } + + // use normal LIST command to return all folders, it's fast enough + $folders = self::$imap->list_folders($root, $mbox, null, null, !empty($postfilter)); + + if (!empty($postfilter)) { + $folders = array_filter($folders, function($folder) use ($postfilter) { return !preg_match($postfilter, $folder); }); + $folders = self::$imap->sort_folder_list($folders); + } + + return $folders; + } + /** * Search for shared or otherwise not listed groupware folders the user has access @@ -959,13 +999,54 @@ class kolab_storage return false; } - $folderdata = self::$imap->get_metadata($prefix, array(self::CTYPE_KEY, self::CTYPE_KEY_PRIVATE)); + // return cached result + if (is_array(self::$typedata[$prefix])) { + return self::$typedata[$prefix]; + } + + $type_keys = array(self::CTYPE_KEY, self::CTYPE_KEY_PRIVATE); + + // fetch metadata from *some* folders only + if (($prefix == '*' || $prefix == '') && ($skip_ns = self::$config->get('kolab_skip_namespace'))) { + $delimiter = self::$imap->get_hierarchy_delimiter(); + $folderdata = $blacklist = array(); + foreach ((array)$skip_ns as $ns) { + if ($ns_root = rtrim(self::namespace_root($ns), $delimiter)) { + $blacklist[] = $ns_root; + } + } + foreach (array('personal','other','shared') as $ns) { + if (!in_array($ns, (array)$skip_ns)) { + $ns_root = rtrim(self::namespace_root($ns), $delimiter); + + // list top-level folders and their childs one by one + // GETMETADATA "%" doesn't list shared or other namespace folders but "*" would + if ($ns_root == '') { + foreach ((array)self::$imap->get_metadata('%', $type_keys) as $folder => $metadata) { + if (!in_array($folder, $blacklist)) { + $folderdata[$folder] = $metadata; + $folderdata += self::$imap->get_metadata($folder.$delimiter.'*', $type_keys); + } + } + } + else { + $folderdata += self::$imap->get_metadata($ns_root.$delimiter.'*', $type_keys); + } + } + } + } + else { + $folderdata = self::$imap->get_metadata($prefix, $type_keys); + } if (!is_array($folderdata)) { return false; } - return array_map(array('kolab_storage', 'folder_select_metadata'), $folderdata); + // keep list in memory + self::$typedata[$prefix] = array_map(array('kolab_storage', 'folder_select_metadata'), $folderdata); + + return self::$typedata[$prefix]; } @@ -996,6 +1077,11 @@ class kolab_storage { self::setup(); + // return in-memory cached result + if (is_array(self::$typedata['*']) && array_key_exists($folder, self::$typedata['*'])) { + return self::$typedata['*'][$folder]; + } + $metadata = self::$imap->get_metadata($folder, array(self::CTYPE_KEY, self::CTYPE_KEY_PRIVATE)); if (!is_array($metadata)) { @@ -1045,7 +1131,9 @@ class kolab_storage { if (self::$subscriptions === null) { self::setup(); + self::$with_tempsubs = false; self::$subscriptions = self::$imap->list_folders_subscribed(); + self::$with_tempsubs = true; } return in_array($folder, self::$subscriptions) || @@ -1177,7 +1265,9 @@ class kolab_storage else { self::setup(); if (self::$subscriptions === null) { + self::$with_tempsubs = false; self::$subscriptions = self::$imap->list_folders_subscribed(); + self::$with_tempsubs = true; } self::$states = self::$subscriptions; $folders = implode(self::$states, '**'); @@ -1372,8 +1462,9 @@ class kolab_storage if (!empty($user[$user_attrib])) { list($mbox) = explode('@', $user[$user_attrib]); + $delimiter = self::$imap->get_hierarchy_delimiter(); $other_ns = self::namespace_root('other'); - $folders = self::list_folders($other_ns . $mbox, '*', $type, $subscribed, $folderdata); + $folders = self::list_folders($other_ns . $mbox . $delimiter, '*', $type, $subscribed, $folderdata); } return $folders; @@ -1383,11 +1474,12 @@ class kolab_storage /** * Get a list of (virtual) top-level folders from the other users namespace * + * @param string Data type to list folders for (contact,event,task,journal,file,note,mail,configuration) * @param boolean Enable to return subscribed folders only (null to use configured subscription mode) * * @return array List of kolab_storage_folder_user objects */ - public static function get_user_folders($subscribed) + public static function get_user_folders($type, $subscribed) { $folders = $folderdata = array(); @@ -1396,7 +1488,7 @@ class kolab_storage $other_ns = rtrim(self::namespace_root('other'), $delimiter); $path_len = count(explode($delimiter, $other_ns)); - foreach ((array)self::list_folders($other_ns, '*', '', $subscribed) as $foldername) { + foreach ((array)self::list_folders($other_ns, '*', $type, $subscribed) as $foldername) { if ($foldername == 'INBOX') // skip INBOX which is added by default continue;