Branch 'dev/new-foldernav' - 2 commits - plugins/calendar plugins/kolab_auth plugins/libkolab
Thomas Brüderli
bruederli at kolabsys.com
Wed May 14 20:42:17 CEST 2014
plugins/calendar/calendar_ui.js | 113 +------
plugins/calendar/drivers/kolab/kolab_calendar.php | 37 +-
plugins/calendar/drivers/kolab/kolab_driver.php | 72 +++-
plugins/calendar/drivers/kolab/kolab_user_calendar.php | 219 +++++++++++++++
plugins/calendar/lib/calendar_ui.php | 32 +-
plugins/calendar/lib/js/folderlist.js | 1
plugins/calendar/skins/larry/calendar.css | 8
plugins/kolab_auth/kolab_auth_ldap.php | 1
plugins/libkolab/js/folderlist.js | 158 ++++++++++
plugins/libkolab/lib/kolab_storage.php | 245 +++++++++++++----
plugins/libkolab/lib/kolab_storage_user_folder.php | 123 ++++++++
plugins/libkolab/lib/kolab_storage_virtual_folder.php | 86 +++++
12 files changed, 899 insertions(+), 196 deletions(-)
New commits:
commit 701c3391fe149a4e82535ae7d48fedd30ce70a77
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date: Wed May 14 20:37:06 2014 +0200
Search in LDAP and collect accessible folders (#3041)
- Add LDAP user search capabilities to kolab_storage class (using kolab_auth plugin classes)
- Introduce virtual 'user' folder objects and add methods to list them
- New 'user calendar' class in calendar (kolab driver)
- Render folder search results as hierarchical list
diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js
index 52baf11..a1a60b1 100644
--- a/plugins/calendar/calendar_ui.js
+++ b/plugins/calendar/calendar_ui.js
@@ -2739,7 +2739,9 @@ function rcube_calendar_ui(settings)
selectable: true,
save_state: true,
searchbox: '#calendarlistsearch',
- search_action: 'calendar/calendar'
+ search_action: 'calendar/calendar',
+ search_sources: [ 'folders', 'users' ],
+ search_title: rcmail.gettext('calsearchresults','calendar')
});
calendars_list.addEventListener('select', function(node) {
me.select_calendar(node.id);
diff --git a/plugins/calendar/drivers/kolab/kolab_calendar.php b/plugins/calendar/drivers/kolab/kolab_calendar.php
index 5feed23..97f6a96 100644
--- a/plugins/calendar/drivers/kolab/kolab_calendar.php
+++ b/plugins/calendar/drivers/kolab/kolab_calendar.php
@@ -7,7 +7,7 @@
* @author Thomas Bruederli <bruederli at kolabsys.com>
* @author Aleksander Machniak <machniak at kolabsys.com>
*
- * Copyright (C) 2012, Kolab Systems AG <contact at kolabsys.com>
+ * Copyright (C) 2012-2014, Kolab Systems AG <contact at 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
@@ -35,11 +35,30 @@ class kolab_calendar
public $storage;
public $name;
- private $cal;
- private $events = array();
- private $imap_folder = 'INBOX/Calendar';
- private $search_fields = array('title', 'description', 'location', 'attendees');
+ protected $cal;
+ protected $events = array();
+ protected $imap_folder = 'INBOX/Calendar';
+ protected $search_fields = array('title', 'description', 'location', 'attendees');
+ /**
+ * Factory method to instantiate a kolab_calendar object
+ *
+ * @param string Calendar ID (encoded IMAP folder name)
+ * @param object calendar plugin object
+ * @return object kolab_calendar instance
+ */
+ public static function factory($id, $calendar)
+ {
+ $imap = $calendar->rc->get_storage();
+ $imap_folder = kolab_storage::id_decode($id);
+ $info = $imap->folder_info($imap_folder, true);
+ if (empty($info) || $info['noselect'] || kolab_storage::folder_type($imap_folder) != 'event') {
+ return new kolab_user_calendar($imap_folder, $calendar);
+ }
+ else {
+ return new kolab_calendar($imap_folder, $calendar);
+ }
+ }
/**
* Default constructor
@@ -177,14 +196,6 @@ class kolab_calendar
return false;
}
- /**
- * Return the corresponding kolab_storage_folder instance
- */
- public function get_folder()
- {
- return $this->storage;
- }
-
/**
* Getter for a single event object
diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php
index 9c45eb6..12e4258 100644
--- a/plugins/calendar/drivers/kolab/kolab_driver.php
+++ b/plugins/calendar/drivers/kolab/kolab_driver.php
@@ -24,6 +24,7 @@
*/
require_once(dirname(__FILE__) . '/kolab_calendar.php');
+require_once(dirname(__FILE__) . '/kolab_user_calendar.php');
class kolab_driver extends calendar_driver
{
@@ -78,14 +79,20 @@ 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'));
+ $folders = kolab_storage::sort_folders(kolab_storage::get_folders('event') + kolab_storage::get_user_folders(true));
$this->calendars = array();
foreach ($folders as $folder) {
- $calendar = new kolab_calendar($folder->name, $this->cal);
- $this->calendars[$calendar->id] = $calendar;
- if (!$calendar->readonly)
- $this->has_writeable = true;
+ if ($folder instanceof kolab_storage_user_folder)
+ $calendar = new kolab_user_calendar($folder->name, $this->cal);
+ else
+ $calendar = new kolab_calendar($folder->name, $this->cal);
+
+ if ($calendar->ready) {
+ $this->calendars[$calendar->id] = $calendar;
+ if (!$calendar->readonly)
+ $this->has_writeable = true;
+ }
}
return $this->calendars;
@@ -114,18 +121,32 @@ class kolab_driver extends calendar_driver
$calendars = $names = array();
// include virtual folders for a full folder tree
- if (!$active && !$personal && !$this->rc->output->ajax_call && in_array($this->rc->action, array('index','')))
+ if (!is_null($tree))
$folders = kolab_storage::folder_hierarchy($folders, $tree);
foreach ($folders as $id => $cal) {
$fullname = $cal->get_name();
- $listname = kolab_storage::folder_displayname($fullname, $names);
+ $listname = $cal->get_foldername();
$imap_path = explode('/', $cal->name);
$topname = array_pop($imap_path);
$parent_id = kolab_storage::folder_id(join('/', $imap_path), true);
- // special handling for virtual folders
- if ($cal->virtual) {
+ // special handling for user or virtual folders
+ if ($cal instanceof kolab_storage_user_folder) {
+ $calendars[$cal->id] = array(
+ 'id' => $cal->id,
+ 'name' => kolab_storage::object_name($fullname),
+ 'listname' => $listname,
+ 'editname' => $cal->get_foldername(),
+ 'color' => $cal->get_color(),
+ 'active' => $cal->is_active(),
+ 'owner' => $cal->get_owner(),
+ 'virtual' => false,
+ 'readonly' => true,
+ 'class_name' => 'user',
+ );
+ }
+ else if ($cal->virtual) {
$calendars[$cal->id] = array(
'id' => $cal->id,
'name' => $fullname,
@@ -230,8 +251,8 @@ class kolab_driver extends calendar_driver
{
// create calendar object if necesary
if (!$this->calendars[$id] && $id !== self::BIRTHDAY_CALENDAR_ID) {
- $foldername = kolab_storage::id_decode($id);
- $calendar = new kolab_calendar($foldername, $this->cal);
+ $calendar = kolab_calendar::factory($id, $this->cal);
+ console($id, $calendar->id, $calendar->ready);
if ($calendar->ready)
$this->calendars[$calendar->id] = $calendar;
}
@@ -389,8 +410,20 @@ class kolab_driver extends calendar_driver
$this->calendars[$calendar->id] = $calendar;
}
}
+ // find other user's virtual calendars
else if ($source == 'users') {
- // TODO: implement this
+ foreach (kolab_storage::search_users($query, 0) as $user) {
+ $calendar = new kolab_user_calendar($user, $this->cal);
+ $this->calendars[$calendar->id] = $calendar;
+
+ // search for calendar folders shared by this user
+ foreach (kolab_storage::list_user_folders($user, 'event', false) as $foldername) {
+ if (1 || !kolab_storage::folder_is_subscribed($foldername, true)) {
+ $cal = new kolab_calendar($foldername, $this->cal);
+ $this->calendars[$cal->id] = $cal;
+ }
+ }
+ }
}
// don't list the birthday calendar
diff --git a/plugins/calendar/drivers/kolab/kolab_user_calendar.php b/plugins/calendar/drivers/kolab/kolab_user_calendar.php
new file mode 100644
index 0000000..a5795a4
--- /dev/null
+++ b/plugins/calendar/drivers/kolab/kolab_user_calendar.php
@@ -0,0 +1,219 @@
+<?php
+
+/**
+ * Kolab calendar storage class simulating a virtual user calendar
+ *
+ * @author Thomas Bruederli <bruederli at kolabsys.com>
+ *
+ * Copyright (C) 2014, Kolab Systems AG <contact at 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_user_calendar extends kolab_calendar
+{
+ public $id = 'unknown';
+ public $ready = false;
+ public $readonly = true;
+ public $attachments = false;
+ public $name;
+
+ protected $userdata = array();
+
+
+ /**
+ * Default constructor
+ */
+ public function __construct($user_or_folder, $calendar)
+ {
+ $this->cal = $calendar;
+
+ // full user record is provided
+ if (is_array($user_or_folder)) {
+ $this->userdata = $user_or_folder;
+ $this->storage = new kolab_storage_user_folder($this->userdata['kolabtargetfolder'], '', $this->userdata);
+ }
+ else { // get user record from LDAP
+ $this->storage = new kolab_storage_user_folder($user_or_folder);
+ $this->userdata = $this->storage->ldaprec;
+ }
+
+ $this->ready = !empty($this->userdata['kolabtargetfolder']);
+
+ if ($this->ready) {
+ // ID is derrived from the user's kolabtargetfolder attribute
+ $this->id = kolab_storage::folder_id($this->userdata['kolabtargetfolder'], true);
+ $this->imap_folder = $this->userdata['kolabtargetfolder'];
+ $this->name = $this->storage->get_name();
+
+ // user-specific alarms settings win
+ $prefs = $this->cal->rc->config->get('kolab_calendars', array());
+ if (isset($prefs[$this->id]['showalarms']))
+ $this->alarms = $prefs[$this->id]['showalarms'];
+ }
+ }
+
+
+ /**
+ * Getter for a nice and human readable name for this calendar
+ *
+ * @return string Name of this calendar
+ */
+ public function get_name()
+ {
+ return $this->userdata['name'] ?: $this->userdata['mail'];
+ }
+
+
+ /**
+ * Getter for the IMAP folder owner
+ *
+ * @return string Name of the folder owner
+ */
+ public function get_owner()
+ {
+ return $this->userdata['mail'];
+ }
+
+
+ /**
+ * Getter for the name of the namespace to which the IMAP folder belongs
+ *
+ * @return string Name of the namespace (personal, other, shared)
+ */
+ public function get_namespace()
+ {
+ return 'user';
+ }
+
+
+ /**
+ * Getter for the top-end calendar folder name (not the entire path)
+ *
+ * @return string Name of this calendar
+ */
+ public function get_foldername()
+ {
+ return $this->get_name();
+ }
+
+ /**
+ * Return color to display this calendar
+ */
+ public function get_color()
+ {
+ // calendar color is stored in local user prefs
+ $prefs = $this->cal->rc->config->get('kolab_calendars', array());
+
+ if (!empty($prefs[$this->id]) && !empty($prefs[$this->id]['color']))
+ return $prefs[$this->id]['color'];
+
+ return 'cc0000';
+ }
+
+ /**
+ * Compose an URL for CalDAV access to this calendar (if configured)
+ */
+ public function get_caldav_url()
+ {
+ return false;
+ }
+
+ /**
+ * Getter for a single event object
+ */
+ public function get_event($id)
+ {
+ // TODO: implement this
+ return $this->events[$id];
+ }
+
+
+ /**
+ * @param integer Event's new start (unix timestamp)
+ * @param integer Event's new end (unix timestamp)
+ * @param string Search query (optional)
+ * @param boolean Include virtual events (optional)
+ * @param array Additional parameters to query storage
+ * @return array A list of event records
+ */
+ public function list_events($start, $end, $search = null, $virtual = 1, $query = array())
+ {
+ // TODO: implement this
+ console('kolab_user_calendar::list_events()');
+ return array();
+ }
+
+
+ /**
+ * Create a new event record
+ *
+ * @see calendar_driver::new_event()
+ *
+ * @return mixed The created record ID on success, False on error
+ */
+ public function insert_event($event)
+ {
+ return false;
+ }
+
+ /**
+ * Update a specific event record
+ *
+ * @see calendar_driver::new_event()
+ * @return boolean True on success, False on error
+ */
+
+ public function update_event($event, $exception_id = null)
+ {
+ return false;
+ }
+
+ /**
+ * Delete an event record
+ *
+ * @see calendar_driver::remove_event()
+ * @return boolean True on success, False on error
+ */
+ public function delete_event($event, $force = true)
+ {
+ return false;
+ }
+
+ /**
+ * Restore deleted event record
+ *
+ * @see calendar_driver::undelete_event()
+ * @return boolean True on success, False on error
+ */
+ public function restore_event($event)
+ {
+ return false;
+ }
+
+
+ /**
+ * Convert from Kolab_Format to internal representation
+ */
+ private function _to_rcube_event($record)
+ {
+ $record['id'] = $record['uid'];
+ $record['calendar'] = $this->id;
+
+ // TODO: implement this
+
+ return $record;
+ }
+
+}
diff --git a/plugins/calendar/lib/calendar_ui.php b/plugins/calendar/lib/calendar_ui.php
index 0f5091a..a3ed443 100644
--- a/plugins/calendar/lib/calendar_ui.php
+++ b/plugins/calendar/lib/calendar_ui.php
@@ -205,6 +205,7 @@ class calendar_ui
{
$html = '';
$jsenv = array();
+ $tree = true;
$calendars = $this->cal->driver->list_calendars(false, false, $tree);
// walk folder tree
@@ -273,19 +274,22 @@ class calendar_ui
*/
public function calendar_list_item($id, $prop, &$jsenv)
{
- unset($prop['user_id']);
- $prop['alarms'] = $this->cal->driver->alarms;
- $prop['attendees'] = $this->cal->driver->attendees;
- $prop['freebusy'] = $this->cal->driver->freebusy;
- $prop['attachments'] = $this->cal->driver->attachments;
- $prop['undelete'] = $this->cal->driver->undelete;
- $prop['feedurl'] = $this->cal->get_url(array('_cal' => $this->cal->ical_feed_hash($id) . '.ics', 'action' => 'feed'));
+ // enrich calendar properties with settings from the driver
+ if (!$prop['virtual']) {
+ unset($prop['user_id']);
+ $prop['alarms'] = $this->cal->driver->alarms;
+ $prop['attendees'] = $this->cal->driver->attendees;
+ $prop['freebusy'] = $this->cal->driver->freebusy;
+ $prop['attachments'] = $this->cal->driver->attachments;
+ $prop['undelete'] = $this->cal->driver->undelete;
+ $prop['feedurl'] = $this->cal->get_url(array('_cal' => $this->cal->ical_feed_hash($id) . '.ics', 'action' => 'feed'));
- if (!$prop['virtual'])
$jsenv[$id] = $prop;
+ }
$class = 'calendar cal-' . asciiwords($id, true);
- $title = $prop['name'] != $prop['listname'] ? html_entity_decode($prop['name'], ENT_COMPAT, RCMAIL_CHARSET) : '';
+ $title = $prop['name'] != $prop['listname'] || strlen($prop['name']) > 25 ?
+ html_entity_decode($prop['name'], ENT_COMPAT, RCMAIL_CHARSET) : '';
$is_collapsed = false; // TODO: determine this somehow?
if ($prop['virtual'])
diff --git a/plugins/calendar/skins/larry/calendar.css b/plugins/calendar/skins/larry/calendar.css
index 01a4c3d..a44899b 100644
--- a/plugins/calendar/skins/larry/calendar.css
+++ b/plugins/calendar/skins/larry/calendar.css
@@ -269,12 +269,12 @@ pre {
#calendars .treelist div.readonly span.calname {
background-position: right -20px;
}
-/*
-#calendars .treelist div.other span.calname {
+
+#calendars .treelist div.user span.calname {
background-position: right -38px;
}
-
-#calendars .treelist div.other.readonly span.calname {
+/*
+#calendars .treelist div.user.readonly span.calname {
background-position: right -56px;
}
diff --git a/plugins/kolab_auth/kolab_auth_ldap.php b/plugins/kolab_auth/kolab_auth_ldap.php
index d529b73..7044ebf 100644
--- a/plugins/kolab_auth/kolab_auth_ldap.php
+++ b/plugins/kolab_auth/kolab_auth_ldap.php
@@ -213,7 +213,6 @@ class kolab_auth_ldap extends rcube_ldap_generic
* 0 - partial (*abc*),
* 1 - strict (=),
* 2 - prefix (abc*)
- * @param boolean $select True if results are requested, False if count only
* @param array $required List of fields that cannot be empty
* @param int $limit Number of records
*
diff --git a/plugins/libkolab/js/folderlist.js b/plugins/libkolab/js/folderlist.js
index eed8b50..e2119a8 100644
--- a/plugins/libkolab/js/folderlist.js
+++ b/plugins/libkolab/js/folderlist.js
@@ -46,7 +46,7 @@ function kolab_folderlist(node, p)
// create treelist widget to present the search results
if (!search_results_widget) {
search_results_container = $('<div class="searchresults"></div>')
- .html('<h2 class="boxtitle">' + rcmail.gettext('calsearchresults','calendar') + '</h2>')
+ .html(p.search_title ? '<h2 class="boxtitle">' + p.search_title + '</h2>' : '')
.insertAfter(me.container);
search_results_widget = new rcube_treelist_widget('<ul class="treelist listing"></ul>', {
@@ -63,36 +63,64 @@ function kolab_folderlist(node, p)
var li = $(this).closest('li'),
id = li.attr('id').replace(new RegExp('^'+p.id_prefix), ''),
+ node = search_results_widget.get_node(id),
prop = search_results[id],
- parent_id = prop.parent || null;
+ 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();
// find parent node and insert at the right place
if (parent_id && $('#' + p.id_prefix + parent_id, me.container).length) {
prop.listname = prop.editname;
- li.children().first().children('span,a').first().html(Q(prop.listname));
+ dom_node.children('span,a').first().html(Q(prop.listname));
}
- // move this result item to the main list widget
- me.insert({
- id: id,
- classes: [],
- html: li.children().first()
- }, parent_id, parent_id ? true : false);
+ // TODO: copy parent tree too
+
+ // replace virtual node with a real one
+ if (me.get_node(id)) {
+ $(me.get_item(id, true)).children().first()
+ .replaceWith(dom_node)
+ .removeClass('virtual');
+ }
+ else {
+ // move this result item to the main list widget
+ me.insert({
+ id: id,
+ classes: [],
+ virtual: prop.virtual,
+ html: dom_node,
+ }, parent_id, parent_id ? true : false);
+ }
delete prop.html;
me.triggerEvent('insert-item', { id: id, data: prop, item: li });
- li.remove();
+
+ if (has_children) {
+ li.find('input[type=checkbox]').first().prop('disabled', true).get(0).checked = true;
+ }
+ else {
+ li.remove();
+ }
});
}
// add results to list
- for (var prop, i=0; i < results.length; i++) {
+ for (var prop, item, i=0; i < results.length; i++) {
prop = results[i];
+ item = $(prop.html);
search_results[prop.id] = prop;
- $('<li>')
- .attr('id', p.id_prefix + prop.id)
- .html(prop.html)
- .appendTo(search_results_widget.container);
+ search_results_widget.insert({
+ id: prop.id,
+ classes: prop.class_name ? String(prop.class_name).split(' ') : [],
+ html: item,
+ collapsed: true
+ }, prop.parent);
+
+ // disable checkbox if item already exists in main list
+ if (me.get_node(prop.id) && !me.get_node(prop.id).virtual) {
+ item.find('input[type=checkbox]').first().prop('disabled', true).get(0).checked = true;
+ }
}
search_results_container.show();
@@ -110,7 +138,7 @@ function kolab_folderlist(node, p)
// send search request(s) to server
if (search.query && search.execute) {
- var sources = [ 'folders' /*, 'users'*/ ];
+ var sources = p.search_sources || [ 'folders' ];
var reqid = rcmail.multi_thread_http_request({
items: sources,
threads: rcmail.env.autocomplete_threads || 1,
diff --git a/plugins/libkolab/lib/kolab_storage.php b/plugins/libkolab/lib/kolab_storage.php
index 970316e..872ce29 100644
--- a/plugins/libkolab/lib/kolab_storage.php
+++ b/plugins/libkolab/lib/kolab_storage.php
@@ -44,6 +44,7 @@ class kolab_storage
private static $states;
private static $config;
private static $imap;
+ private static $ldap;
// Default folder names
private static $default_folders = array(
@@ -101,6 +102,40 @@ class kolab_storage
return self::$ready;
}
+ /**
+ * Initializes LDAP object to resolve Kolab users
+ */
+ public static function ldap()
+ {
+ if (self::$ldap) {
+ return self::$ldap;
+ }
+
+ $rcmail = rcube::get_instance();
+ $config = $rcmail->config->get('kolab_users_directory', $rcmail->config->get('kolab_auth_addressbook'));
+
+ if (!is_array($config)) {
+ $ldap_config = (array)$rcmail->config->get('ldap_public');
+ $config = $ldap_config[$config];
+ }
+
+ if (empty($config)) {
+ return null;
+ }
+
+ // overwrite filter option
+ if ($filter = $rcmail->config->get('kolab_users_filter')) {
+ $rcmail->config->set('kolab_auth_filter', $filter);
+ }
+
+ // re-use the LDAP wrapper class from kolab_auth plugin
+ require_once rtrim(RCUBE_PLUGINS_DIR, '/') . '/kolab_auth/kolab_auth_ldap.php';
+
+ self::$ldap = new kolab_auth_ldap($config);
+
+ return self::$ldap;
+ }
+
/**
* Get a list of storage folders for the given data type
@@ -490,7 +525,7 @@ class kolab_storage
$folder = substr($folder, $pos+1);
}
else {
- $prefix = $folder;
+ $prefix = '('.$folder.')';
$folder = '';
}
@@ -811,7 +846,7 @@ class kolab_storage
// $folders is a result of get_folders() we can assume folders were already sorted
foreach (array_keys($nsnames) as $ns) {
- // asort($nsnames[$ns], SORT_LOCALE_STRING);
+ asort($nsnames[$ns], SORT_LOCALE_STRING);
foreach (array_keys($nsnames[$ns]) as $utf7name) {
$out[] = $folders[$utf7name];
}
@@ -829,13 +864,18 @@ class kolab_storage
*
* @return array Flat folders list
*/
- public static function folder_hierarchy($folders, &$tree)
+ public static function folder_hierarchy($folders, &$tree = null)
{
$_folders = array();
- $delim = rcube::get_instance()->get_storage()->get_hierarchy_delimiter();
- $tree = new virtual_kolab_storage_folder('', '<root>', ''); // create tree root
+ $delim = self::$imap->get_hierarchy_delimiter();
+ $other_ns = self::$imap->get_namespace('other');
+ $tree = new kolab_storage_virtual_folder('', '<root>', ''); // create tree root
$refs = array('' => $tree);
+ if (is_array($other_ns)) {
+ $other_ns = rtrim($other_ns[0][0], '/');
+ }
+
foreach ($folders as $idx => $folder) {
$path = explode($delim, $folder->name);
array_pop($path);
@@ -852,9 +892,15 @@ class kolab_storage
while (count($path) >= $depth && ($parent = join($delim, $path))) {
array_pop($path);
- $name = kolab_storage::object_name($parent, $folder->get_namespace());
+ $parent_parent = join($delim, $path);
if (!$refs[$parent]) {
- $refs[$parent] = new virtual_kolab_storage_folder($parent, $name, $folder->get_namespace(), join($delim, $path));
+ if ($parent_parent == $other_ns) {
+ $refs[$parent] = new kolab_storage_user_folder($parent, $parent_parent);
+ }
+ else {
+ $name = kolab_storage::object_name($parent, $folder->get_namespace());
+ $refs[$parent] = new kolab_storage_virtual_folder($parent, $name, $folder->get_namespace(), $parent_parent);
+ }
$parents[] = $refs[$parent];
}
}
@@ -1023,14 +1069,22 @@ class kolab_storage
* Change subscription status of this folder
*
* @param string $folder Folder name
+ * @param boolean $temp Only remove temporary subscription
*
* @return True on success, false on error
*/
- public static function folder_unsubscribe($folder)
+ public static function folder_unsubscribe($folder, $temp = false)
{
self::setup();
- if (self::$imap->unsubscribe($folder)) {
+ // temporary/session subscription
+ if ($temp) {
+ if (is_array($_SESSION['kolab_subscribed_folders']) && ($i = array_search($folder, $_SESSION['kolab_subscribed_folders'])) !== false) {
+ unset($_SESSION['kolab_subscribed_folders'][$i]);
+ }
+ return true;
+ }
+ else if (self::$imap->unsubscribe($folder)) {
self::$subscriptions === null;
return true;
}
@@ -1078,10 +1132,8 @@ class kolab_storage
*/
public static function folder_deactivate($folder)
{
- // remove from temp subscriptions
- if (is_array($_SESSION['kolab_subscribed_folders']) && ($i = array_search($folder, $_SESSION['kolab_subscribed_folders'])) !== false) {
- unset($_SESSION['kolab_subscribed_folders'][$i]);
- }
+ // remove from temp subscriptions, really?
+ self::folder_unsubscribe($folder, true);
return self::set_state($folder, false);
}
@@ -1241,57 +1293,117 @@ class kolab_storage
}
}
+
/**
- * Handler for user_delete plugin hooks
*
- * Remove all cache data from the local database related to the given user.
+ * @param mixed $query Search value (or array of field => value pairs)
+ * @param int $mode Matching mode: 0 - partial (*abc*), 1 - strict (=), 2 - prefix (abc*)
+ * @param array $required List of fields that shall ot be empty
+ * @param int $limit Number of records
+ *
+ * @return array List or false on error
*/
- public static function delete_user_folders($args)
+ public static function search_users($query, $mode = 1, $required = array(), $limit = 0)
{
- $db = rcmail::get_instance()->get_dbh();
- $prefix = 'imap://' . urlencode($args['username']) . '@' . $args['host'] . '/%';
- $db->query("DELETE FROM " . $db->table_name('kolab_folders') . " WHERE resource LIKE ?", $prefix);
- }
+ // requires a working LDAP setup
+ if (!self::ldap()) {
+ return array();
+ }
-}
+ // FIXME: make search attributes configurable
+ $results = self::$ldap->search(array('cn','mail','alias'), $query, $mode, $required, $limit);
-/**
- * Helper class that represents a virtual IMAP folder
- * with a subset of the kolab_storage_folder API.
- */
-class virtual_kolab_storage_folder
-{
- public $id;
- public $name;
- public $namespace;
- public $parent = '';
- public $children = array();
- public $virtual = true;
- protected $displayname;
-
- public function __construct($name, $dispname, $ns, $parent = '')
- {
- $this->id = kolab_storage::folder_id($name);
- $this->name = $name;
- $this->namespace = $ns;
- $this->parent = $parent;
- $this->displayname = $dispname;
+ // resolve to IMAP folder name
+ $other_ns = self::$imap->get_namespace('other');
+ $user_attrib = rcube::get_instance()->config->get('kolab_auth_login', 'mail');
+
+ array_walk($results, function(&$user, $dn) use ($other_ns, $user_attrib) {
+ list($localpart, $domain) = explode('@', $user[$user_attrib]);
+ $root = $other_ns[0][0];
+ $user['kolabtargetfolder'] = $root . $localpart;
+ });
+
+ return $results;
}
- public function get_namespace()
+
+ /**
+ * Returns a list of IMAP folders shared by the given user
+ *
+ * @param array User entry from LDAP
+ * @param string Data type to list folders for (contact,event,task,journal,file,note,mail,configuration)
+ * @param boolean Return subscribed folders only (null to use configured subscription mode)
+ * @param array Will be filled with folder-types data
+ *
+ * @return array List of folders
+ */
+ public static function list_user_folders($user, $type, $subscribed = null, &$folderdata = array())
{
- return $this->namespace;
+ $folders = array();
+
+ // use localpart of user attribute as root for folder listing
+ $user_attrib = rcube::get_instance()->config->get('kolab_auth_login', 'mail');
+ if (!empty($user[$user_attrib])) {
+ list($mbox) = explode('@', $user[$user_attrib]);
+
+ $other_ns = self::$imap->get_namespace('other');
+ if (is_array($other_ns)) {
+ $other_ns = $other_ns[0][0];
+ }
+
+ $folders = self::list_folders($other_ns . $mbox, '*', $type, $subscribed, $folderdata);
+ }
+
+ return $folders;
}
- public function get_name()
+
+ /**
+ * Get a list of (virtual) top-level folders from the other users namespace
+ *
+ * @param boolean Enable to return subscribed folders only (null to use configured subscription mode)
+ *
+ * @return array List of Kolab_Folder objects (folder names in UTF7-IMAP)
+ */
+ public static function get_user_folders($subscribed)
{
- // this is already kolab_storage::object_name() result
- return $this->displayname;
+ $folders = $folderdata = array();
+
+ if (self::setup()) {
+ $delimiter = self::$imap->get_hierarchy_delimiter();
+ $other_ns = self::$imap->get_namespace('other');
+ if (is_array($other_ns)) {
+ $other_ns = rtrim($other_ns[0][0], $delimiter);
+ $other_depth = count(explode($delimiter, $other_ns));
+ }
+
+ foreach ((array)self::list_folders($other_ns, '*', '', $subscribed) as $foldername) {
+ $path = explode($delimiter, $foldername);
+ $depth = count($path) - $other_depth;
+ array_pop($path);
+
+ // only list top-level folders of the 'other' namespace
+ if ($depth == 1) {
+ $folders[$foldername] = new kolab_storage_user_folder($foldername, $other_ns);
+ }
+ }
+ }
+
+ return $folders;
}
- public function get_foldername()
+
+ /**
+ * Handler for user_delete plugin hooks
+ *
+ * Remove all cache data from the local database related to the given user.
+ */
+ public static function delete_user_folders($args)
{
- $parts = explode('/', $this->name);
- return rcube_charset::convert(end($parts), 'UTF7-IMAP');
+ $db = rcmail::get_instance()->get_dbh();
+ $prefix = 'imap://' . urlencode($args['username']) . '@' . $args['host'] . '/%';
+ $db->query("DELETE FROM " . $db->table_name('kolab_folders') . " WHERE resource LIKE ?", $prefix);
}
+
}
+
diff --git a/plugins/libkolab/lib/kolab_storage_user_folder.php b/plugins/libkolab/lib/kolab_storage_user_folder.php
new file mode 100644
index 0000000..55e38a0
--- /dev/null
+++ b/plugins/libkolab/lib/kolab_storage_user_folder.php
@@ -0,0 +1,123 @@
+<?php
+
+/**
+ * Class that represents a (virtual) folder in the 'other' namespace
+ * implementing a subset of the kolab_storage_folder API.
+ *
+ * @author Thomas Bruederli <bruederli at kolabsys.com>
+ *
+ * Copyright (C) 2014, Kolab Systems AG <contact at 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_storage_user_folder extends kolab_storage_virtual_folder
+{
+ protected static $ldapcache = array();
+
+ public $ldaprec;
+
+ /**
+ * Default constructor
+ */
+ public function __construct($name, $parent = '', $ldaprec = null)
+ {
+ parent::__construct($name, $name, 'other', $parent);
+
+ if (!empty($ldaprec)) {
+ self::$ldapcache[$name] = $this->ldaprec = $ldaprec;
+ }
+ // use value cached in memory for repeated lookups
+ else if (array_key_exists($name, self::$ldapcache)) {
+ $this->ldaprec = self::$ldapcache[$name];
+ }
+ // lookup user in LDAP and set $this->ldaprec
+ else if ($ldap = kolab_storage::ldap()) {
+ // get domain from current user
+ list(,$domain) = explode('@', rcube::get_instance()->get_user_name());
+ $this->ldaprec = $ldap->get_user_record(parent::get_foldername($this->name) . '@' . $domain, $_SESSION['imap_host']);
+ if (!empty($this->ldaprec)) {
+ $this->ldaprec['kolabtargetfolder'] = $name;
+ }
+ self::$ldapcache[$name] = $this->ldaprec;
+ }
+ }
+
+ /**
+ * Getter for the top-end folder name to be displayed
+ *
+ * @return string Name of this folder
+ */
+ public function get_foldername()
+ {
+ return $this->ldaprec ? ($this->ldaprec['displayname'] ?: $this->ldaprec['name']) :
+ parent::get_foldername();
+ }
+
+ /**
+ * Returns the owner of the folder.
+ *
+ * @return string The owner of this folder.
+ */
+ public function get_owner()
+ {
+ return $this->ldaprec['mail'];
+ }
+
+ /**
+ * Check activation status of this folder
+ *
+ * @return boolean True if enabled, false if not
+ */
+ public function is_active()
+ {
+ return kolab_storage::folder_is_active($this->name);
+ }
+
+ /**
+ * Change activation status of this folder
+ *
+ * @param boolean The desired subscription status: true = active, false = not active
+ *
+ * @return True on success, false on error
+ */
+ public function activate($active)
+ {
+ return $active ? kolab_storage::folder_activate($this->name) : kolab_storage::folder_deactivate($this->name);
+ }
+
+ /**
+ * Check subscription status of this folder
+ *
+ * @return boolean True if subscribed, false if not
+ */
+ public function is_subscribed()
+ {
+ return kolab_storage::folder_is_subscribed($this->name, true);
+ }
+
+ /**
+ * Change subscription status of this folder
+ *
+ * @param boolean The desired subscription status: true = subscribed, false = not subscribed
+ *
+ * @return True on success, false on error
+ */
+ public function subscribe($subscribed)
+ {
+ return $subscribed ?
+ kolab_storage::folder_subscribe($this->name, true) :
+ kolab_storage::folder_unsubscribe($this->name, true);
+ }
+
+}
\ No newline at end of file
diff --git a/plugins/libkolab/lib/kolab_storage_virtual_folder.php b/plugins/libkolab/lib/kolab_storage_virtual_folder.php
new file mode 100644
index 0000000..61d0fe0
--- /dev/null
+++ b/plugins/libkolab/lib/kolab_storage_virtual_folder.php
@@ -0,0 +1,86 @@
+<?php
+
+/**
+ * Helper class that represents a virtual IMAP folder
+ * with a subset of the kolab_storage_folder API.
+ *
+ * @author Thomas Bruederli <bruederli at kolabsys.com>
+ *
+ * Copyright (C) 2014, Kolab Systems AG <contact at 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_storage_virtual_folder
+{
+ public $id;
+ public $name;
+ public $namespace;
+ public $parent = '';
+ public $children = array();
+ public $virtual = true;
+
+ protected $displayname;
+
+ public function __construct($name, $dispname, $ns, $parent = '')
+ {
+ $this->id = kolab_storage::folder_id($name);
+ $this->name = $name;
+ $this->namespace = $ns;
+ $this->parent = $parent;
+ $this->displayname = $dispname;
+ }
+
+ /**
+ * Getter for the name of the namespace to which the IMAP folder belongs
+ *
+ * @return string Name of the namespace (personal, other, shared)
+ */
+ public function get_namespace()
+ {
+ return $this->namespace;
+ }
+
+ /**
+ * Get the display name value of this folder
+ *
+ * @return string Folder name
+ */
+ public function get_name()
+ {
+ // this is already kolab_storage::object_name() result
+ return $this->displayname;
+ }
+
+ /**
+ * Getter for the top-end folder name (not the entire path)
+ *
+ * @return string Name of this folder
+ */
+ public function get_foldername()
+ {
+ $parts = explode('/', $this->name);
+ return rcube_charset::convert(end($parts), 'UTF7-IMAP');
+ }
+
+ /**
+ * Get the color value stored in metadata
+ *
+ * @param string Default color value to return if not set
+ * @return mixed Color value from IMAP metadata or $default is not set
+ */
+ public function get_color($default = null)
+ {
+ return $default;
+ }
+}
\ No newline at end of file
commit 8a47c676d52645f424a5234c9c6d2fdfa7536f49
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date: Tue May 13 19:14:08 2014 +0200
Move new calendar list widget and folder searching to libkolab for shared use
diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js
index 4db8593..52baf11 100644
--- a/plugins/calendar/calendar_ui.js
+++ b/plugins/calendar/calendar_ui.js
@@ -1548,7 +1548,8 @@ function rcube_calendar_ui(settings)
id_prefix: 'rcres',
id_encode: rcmail.html_identifier_encode,
id_decode: rcmail.html_identifier_decode,
- selectable: true
+ selectable: true,
+ save_state: true
});
resources_treelist.addEventListener('select', function(node) {
if (resources_data[node.id]) {
@@ -2671,72 +2672,6 @@ function rcube_calendar_ui(settings)
this.selected_calendar = id;
};
- // render the results for calendar list search
- var calendar_search_results = function(results)
- {
- if (results.length) {
- // create treelist widget to present the search results
- if (!calenders_search_list) {
- calenders_search_container = $('<div class="searchresults"></div>')
- .html('<h2 class="boxtitle">' + rcmail.gettext('calsearchresults','calendar') + '</h2>')
- .insertAfter(rcmail.gui_objects.calendarslist);
-
- calenders_search_list = new rcube_treelist_widget('<ul class="treelist listing"></ul>', {
- id_prefix: 'rcmlical',
- selectable: false
- });
-
- // register click handler on search result's checkboxes to select the given calendar for listing
- calenders_search_list.container
- .appendTo(calenders_search_container)
- .on('click', 'input[type=checkbox]', function(e){
- var li = $(this).closest('li'),
- id = li.attr('id').replace(/^rcmlical/, ''),
- prop = search_calendars[id],
- parent_id = prop.parent || null;
-
- if (!this.checked)
- return;
-
- // find parent node and insert at the right place
- if (parent_id && $('#rcmlical'+parent_id, rcmail.gui_objects.calendarslist).length) {
- prop.listname = prop.editname;
- li.children().first().find('.calname').html(Q(prop.listname));
- }
-
- // move this calendar to the calendars_list widget
- calendars_list.insert({
- id: id,
- classes: [],
- html: li.children().first()
- }, parent_id, parent_id ? true : false);
-
- search_calendars[id].active = true;
- add_calendar_source(prop);
- li.remove();
-
- // add css classes related to this calendar to document
- if (cal.css) {
- $('<style type="text/css"></style>')
- .html(cal.css)
- .appendTo('head');
- }
- });
- }
-
- for (var cal, i=0; i < results.length; i++) {
- cal = results[i];
- search_calendars[cal.id] = cal;
- $('<li>')
- .attr('id', 'rcmlical' + cal.id)
- .html(cal.html)
- .appendTo(calenders_search_list.container);
- }
-
- calenders_search_container.show();
- }
- };
-
// register the given calendar to the current view
var add_calendar_source = function(cal)
{
@@ -2798,37 +2733,31 @@ function rcube_calendar_ui(settings)
}
// initialize treelist widget that controls the calendars list
- calendars_list = new rcube_treelist_widget(rcmail.gui_objects.calendarslist, {
+ var widget_class = window.kolab_folderlist || rcube_treelist_widget;
+ calendars_list = new widget_class(rcmail.gui_objects.calendarslist, {
id_prefix: 'rcmlical',
selectable: true,
- searchbox: '#calendarlistsearch'
+ save_state: true,
+ searchbox: '#calendarlistsearch',
+ search_action: 'calendar/calendar'
});
- calendars_list.addEventListener('select', function(node){
+ calendars_list.addEventListener('select', function(node) {
me.select_calendar(node.id);
rcmail.enable_command('calendar-edit', 'calendar-showurl', true);
rcmail.enable_command('calendar-remove', !me.calendars[node.id].readonly);
});
- calendars_list.addEventListener('search', function(search){
- // hide search results
- if (calenders_search_list) {
- calenders_search_container.hide();
- calenders_search_list.reset();
- }
- search_calendars = {};
-
- // send search request(s) to server
- if (search.query && search.execute) {
- var sources = [ 'folders' /*, 'users'*/ ];
- var reqid = rcmail.multi_thread_http_request({
- items: sources,
- threads: rcmail.env.autocomplete_threads || 1,
- action: 'calendar/calendar',
- postdata: { action:'search', q:search.query, source:'%s' },
- lock: rcmail.display_message(rcmail.get_label('searching'), 'loading'),
- onresponse: calendar_search_results
- });
-
- listsearch_data = { id:reqid, sources:sources.slice(), num:sources.length };
+ calendars_list.addEventListener('insert-item', function(p) {
+ var cal = p.data;
+ if (cal && cal.id) {
+ cal.active = true;
+ add_calendar_source(cal);
+
+ // add css classes related to this calendar to document
+ if (cal.css) {
+ $('<style type="text/css"></style>')
+ .html(cal.css)
+ .appendTo('head');
+ }
}
});
diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php
index 786556f..9c45eb6 100644
--- a/plugins/calendar/drivers/kolab/kolab_driver.php
+++ b/plugins/calendar/drivers/kolab/kolab_driver.php
@@ -381,23 +381,10 @@ class kolab_driver extends calendar_driver
return array();
$this->calendars = array();
- $imap = $this->rc->get_storage();
// find unsubscribed IMAP folders that have "event" type
if ($source == 'folders') {
- $folders = array();
- foreach ((array)kolab_storage::list_folders('', '*', 'event', false, $folderdata) as $foldername) {
- // FIXME: only consider the last part of the folder path for searching?
- $realname = strtolower(rcube_charset::convert($foldername, 'UTF7-IMAP'));
- if (strpos($realname, $query) !== false &&
- !kolab_storage::folder_is_subscribed($foldername, true) &&
- $imap->folder_namespace($foldername) != 'other'
- ) {
- $folders[$foldername] = new kolab_storage_folder($foldername, $folderdata[$foldername]);
- }
- }
-
- foreach ($folders as $folder) {
+ foreach ((array)kolab_storage::search_folders('event', $query, array('other')) as $folder) {
$calendar = new kolab_calendar($folder->name, $this->cal);
$this->calendars[$calendar->id] = $calendar;
}
diff --git a/plugins/calendar/lib/calendar_ui.php b/plugins/calendar/lib/calendar_ui.php
index 69ba9c2..0f5091a 100644
--- a/plugins/calendar/lib/calendar_ui.php
+++ b/plugins/calendar/lib/calendar_ui.php
@@ -117,6 +117,12 @@ class calendar_ui
$this->cal->include_script('calendar_ui.js');
$this->cal->include_script('lib/js/fullcalendar.js');
$this->rc->output->include_script('treelist.js');
+
+ // include kolab folderlist widget if available
+ if (is_readable($this->cal->home . '/lib/js/folderlist.js')) {
+ $this->cal->include_script('lib/js/folderlist.js');
+ }
+
jqueryui::miniColors();
}
@@ -292,9 +298,9 @@ class calendar_ui
$content = '';
if (!$attrib['activeonly'] || $prop['active']) {
$content = html::div($class,
+ html::span(array('class' => 'calname', 'title' => $title), $prop['editname'] ? Q($prop['editname']) : $prop['listname']) .
($prop['virtual'] ? '' : html::tag('input', array('type' => 'checkbox', 'name' => '_cal[]', 'value' => $id, 'checked' => $prop['active']), '') .
- html::span(array('class' => 'handle', 'style' => "background-color: #" . ($prop['color'] ?: 'f00')), ' ')) .
- html::span(array('class' => 'calname', 'title' => $title), $prop['editname'] ? Q($prop['editname']) : $prop['listname'])
+ html::span(array('class' => 'handle', 'style' => "background-color: #" . ($prop['color'] ?: 'f00')), ' '))
);
}
diff --git a/plugins/calendar/lib/js/folderlist.js b/plugins/calendar/lib/js/folderlist.js
new file mode 120000
index 0000000..c49706b
--- /dev/null
+++ b/plugins/calendar/lib/js/folderlist.js
@@ -0,0 +1 @@
+../../../libkolab/js/folderlist.js
\ No newline at end of file
diff --git a/plugins/libkolab/js/folderlist.js b/plugins/libkolab/js/folderlist.js
new file mode 100644
index 0000000..eed8b50
--- /dev/null
+++ b/plugins/libkolab/js/folderlist.js
@@ -0,0 +1,130 @@
+/**
+ * Kolab groupware folders treelist widget
+ *
+ * @author Thomas Bruederli <bruederli at kolabsys.com>
+ *
+ * @licstart The following is the entire license notice for the
+ * JavaScript code in this file.
+ *
+ * Copyright (C) 2014, Kolab Systems AG <contact at 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.
+ */
+
+function kolab_folderlist(node, p)
+{
+ // extends treelist.js
+ rcube_treelist_widget.call(this, node, p);
+
+ // private vars
+ var me = this;
+ var search_results;
+ var search_results_widget;
+ var search_results_container;
+ var listsearch_request;
+
+ var Q = rcmail.quote_html;
+
+ // render the results for folderlist search
+ function render_search_results(results)
+ {
+ if (results.length) {
+ // create treelist widget to present the search results
+ if (!search_results_widget) {
+ search_results_container = $('<div class="searchresults"></div>')
+ .html('<h2 class="boxtitle">' + rcmail.gettext('calsearchresults','calendar') + '</h2>')
+ .insertAfter(me.container);
+
+ search_results_widget = new rcube_treelist_widget('<ul class="treelist listing"></ul>', {
+ id_prefix: p.id_prefix,
+ selectable: false
+ });
+
+ // register click handler on search result's checkboxes to select the given item for listing
+ search_results_widget.container
+ .appendTo(search_results_container)
+ .on('click', 'input[type=checkbox]', function(e) {
+ if (!this.checked)
+ return;
+
+ var li = $(this).closest('li'),
+ id = li.attr('id').replace(new RegExp('^'+p.id_prefix), ''),
+ prop = search_results[id],
+ parent_id = prop.parent || null;
+
+ // find parent node and insert at the right place
+ if (parent_id && $('#' + p.id_prefix + parent_id, me.container).length) {
+ prop.listname = prop.editname;
+ li.children().first().children('span,a').first().html(Q(prop.listname));
+ }
+
+ // move this result item to the main list widget
+ me.insert({
+ id: id,
+ classes: [],
+ html: li.children().first()
+ }, parent_id, parent_id ? true : false);
+
+ delete prop.html;
+ me.triggerEvent('insert-item', { id: id, data: prop, item: li });
+ li.remove();
+ });
+ }
+
+ // add results to list
+ for (var prop, i=0; i < results.length; i++) {
+ prop = results[i];
+ search_results[prop.id] = prop;
+ $('<li>')
+ .attr('id', p.id_prefix + prop.id)
+ .html(prop.html)
+ .appendTo(search_results_widget.container);
+ }
+
+ search_results_container.show();
+ }
+ }
+
+ // do some magic when search is performed on the widget
+ this.addEventListener('search', function(search) {
+ // hide search results
+ if (search_results_widget) {
+ search_results_container.hide();
+ search_results_widget.reset();
+ }
+ search_results = {};
+
+ // send search request(s) to server
+ if (search.query && search.execute) {
+ var sources = [ 'folders' /*, 'users'*/ ];
+ var reqid = rcmail.multi_thread_http_request({
+ items: sources,
+ threads: rcmail.env.autocomplete_threads || 1,
+ action: p.search_action || 'listsearch',
+ postdata: { action:'search', q:search.query, source:'%s' },
+ lock: rcmail.display_message(rcmail.get_label('searching'), 'loading'),
+ onresponse: render_search_results
+ });
+
+ listsearch_request = { id:reqid, sources:sources.slice(), num:sources.length };
+ }
+ });
+
+}
+
+// link prototype from base class
+kolab_folderlist.prototype = rcube_treelist_widget.prototype;
diff --git a/plugins/libkolab/lib/kolab_storage.php b/plugins/libkolab/lib/kolab_storage.php
index 55526fd..970316e 100644
--- a/plugins/libkolab/lib/kolab_storage.php
+++ b/plugins/libkolab/lib/kolab_storage.php
@@ -759,6 +759,39 @@ class kolab_storage
/**
+ * Search for shared or otherwise not listed groupware folders the user has access
+ *
+ * @param string Folder type of folders to search for
+ * @param string Search string
+ * @param array Namespace(s) to exclude results from
+ *
+ * @return array List of matching kolab_storage_folder objects
+ */
+ public static function search_folders($type, $query, $exclude_ns = array())
+ {
+ if (!self::setup()) {
+ return array();
+ }
+
+ $folders = array();
+
+ // find unsubscribed IMAP folders of the given type
+ foreach ((array)self::list_folders('', '*', $type, false, $folderdata) as $foldername) {
+ // FIXME: only consider the last part of the folder path for searching?
+ $realname = strtolower(rcube_charset::convert($foldername, 'UTF7-IMAP'));
+ if (strpos($realname, $query) !== false &&
+ !self::folder_is_subscribed($foldername, true) &&
+ !in_array(self::$imap->folder_namespace($foldername), (array)$exclude_ns)
+ ) {
+ $folders[] = new kolab_storage_folder($foldername, $folderdata[$foldername]);
+ }
+ }
+
+ return $folders;
+ }
+
+
+ /**
* Sort the given list of kolab folders by namespace/name
*
* @param array List of kolab_storage_folder objects
More information about the commits
mailing list