Branch 'dev/new-foldernav' - 3 commits - plugins/calendar plugins/libkolab

Thomas Brüderli bruederli at kolabsys.com
Thu May 15 13:16:09 CEST 2014


 plugins/calendar/calendar_ui.js                        |    3 
 plugins/calendar/drivers/kolab/kolab_calendar.php      |   78 +---
 plugins/calendar/drivers/kolab/kolab_driver.php        |   31 -
 plugins/calendar/drivers/kolab/kolab_user_calendar.php |   19 -
 plugins/calendar/lib/calendar_ui.php                   |    4 
 plugins/calendar/localization/en_US.inc                |    2 
 plugins/calendar/skins/larry/calendar.css              |    4 
 plugins/libkolab/js/folderlist.js                      |   78 ++--
 plugins/libkolab/lib/kolab_storage.php                 |   65 ++-
 plugins/libkolab/lib/kolab_storage_folder.php          |  188 -----------
 plugins/libkolab/lib/kolab_storage_folder_api.php      |  280 +++++++++++++++++
 plugins/libkolab/lib/kolab_storage_folder_user.php     |  101 ++++++
 plugins/libkolab/lib/kolab_storage_folder_virtual.php  |   60 +++
 plugins/libkolab/lib/kolab_storage_user_folder.php     |  123 -------
 plugins/libkolab/lib/kolab_storage_virtual_folder.php  |   86 -----
 15 files changed, 598 insertions(+), 524 deletions(-)

New commits:
commit 510089523ee8753733e0678e93760baa15441a23
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Thu May 15 13:15:58 2014 +0200

    Refactored kolab_storage_folder classes and consolidated some functions

diff --git a/plugins/calendar/drivers/kolab/kolab_calendar.php b/plugins/calendar/drivers/kolab/kolab_calendar.php
index 374ab5d..c4b7dbb 100644
--- a/plugins/calendar/drivers/kolab/kolab_calendar.php
+++ b/plugins/calendar/drivers/kolab/kolab_calendar.php
@@ -24,9 +24,8 @@
  */
 
 
-class kolab_calendar
+class kolab_calendar extends kolab_storage_folder_api
 {
-  public $id;
   public $ready = false;
   public $readonly = true;
   public $attachments = true;
@@ -35,11 +34,9 @@ class kolab_calendar
   public $storage;
 
   public $type = 'event';
-  public $name;
 
   protected $cal;
   protected $events = array();
-  protected $imap_folder = 'INBOX/Calendar';
   protected $search_fields = array('title', 'description', 'location', 'attendees');
 
   /**
@@ -68,16 +65,15 @@ class kolab_calendar
   public function __construct($imap_folder, $calendar)
   {
     $this->cal = $calendar;
-
-    if (strlen($imap_folder))
-      $this->imap_folder = $this->name = $imap_folder;
+    $this->imap = $calendar->rc->get_storage();
+    $this->name = $imap_folder;
 
     // ID is derrived from folder name
-    $this->id = kolab_storage::folder_id($this->imap_folder, true);
-    $old_id   = kolab_storage::folder_id($this->imap_folder, false);
+    $this->id = kolab_storage::folder_id($this->name, true);
+    $old_id   = kolab_storage::folder_id($this->name, false);
 
     // fetch objects from the given IMAP folder
-    $this->storage = kolab_storage::get_folder($this->imap_folder);
+    $this->storage = kolab_storage::get_folder($this->name);
     $this->ready = $this->storage && !PEAR::isError($this->storage) && $this->storage->type !== null;
 
     // Set readonly and alarms flags according to folder permissions
@@ -101,6 +97,8 @@ class kolab_calendar
       else if (isset($prefs[$old_id]['showalarms']))
         $this->alarms = $prefs[$old_id]['showalarms'];
     }
+
+    $this->default = $this->storage->default;
   }
 
 
@@ -112,7 +110,7 @@ class kolab_calendar
    */
   public function get_name()
   {
-    $folder = kolab_storage::object_name($this->imap_folder, $this->namespace);
+    $folder = kolab_storage::object_name($this->name, $this->namespace);
     return $folder;
   }
 
@@ -124,44 +122,11 @@ class kolab_calendar
    */
   public function get_realname()
   {
-    return $this->imap_folder;
-  }
-
-
-  /**
-   * Getter for the IMAP folder owner
-   *
-   * @return string Name of the folder owner
-   */
-  public function get_owner()
-  {
-    return $this->storage->get_owner();
+    return $this->name;
   }
 
 
   /**
-   * 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->storage->get_namespace();
-  }
-
-
-  /**
-   * Getter for the top-end calendar folder name (not the entire path)
-   *
-   * @return string Name of this calendar
-   */
-  public function get_foldername()
-  {
-    $parts = explode('/', $this->imap_folder);
-    return rcube_charset::convert(end($parts), 'UTF7-IMAP');
-  }
-
-  /**
    * Return color to display this calendar
    */
   public function get_color()
@@ -191,7 +156,7 @@ class kolab_calendar
         '%h' => $_SERVER['HTTP_HOST'],
         '%u' => urlencode($this->cal->rc->get_user_name()),
         '%i' => urlencode($this->storage->get_uid()),
-        '%n' => urlencode($this->imap_folder),
+        '%n' => urlencode($this->name),
       ));
     }
 
diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php
index 13c460e..d1bb655 100644
--- a/plugins/calendar/drivers/kolab/kolab_driver.php
+++ b/plugins/calendar/drivers/kolab/kolab_driver.php
@@ -83,7 +83,7 @@ class kolab_driver extends calendar_driver
     $this->calendars = array();
 
     foreach ($folders as $folder) {
-      if ($folder instanceof kolab_storage_user_folder)
+      if ($folder instanceof kolab_storage_folder_user)
         $calendar = new kolab_user_calendar($folder->name, $this->cal);
       else
         $calendar = new kolab_calendar($folder->name, $this->cal);
@@ -138,7 +138,7 @@ class kolab_driver extends calendar_driver
       }
 
       // special handling for user or virtual folders
-      if ($cal instanceof kolab_storage_user_folder) {
+      if ($cal instanceof kolab_storage_folder_user) {
         $calendars[$cal->id] = array(
           'id' => $cal->id,
           'name' => kolab_storage::object_name($fullname),
@@ -172,8 +172,8 @@ class kolab_driver extends calendar_driver
           'readonly' => $cal->readonly,
           'showalarms' => $cal->alarms,
           'class_name' => $cal->get_namespace(),
-          'default'  => $cal->storage->default,
-          'active'   => $cal->storage->is_active(),
+          'default'  => $cal->default,
+          'active'   => $cal->is_active(),
           'owner'    => $cal->get_owner(),
           'children' => true,  // TODO: determine if that folder indeed has child folders
           'parent'   => $parent_id,
@@ -234,7 +234,7 @@ class kolab_driver extends calendar_driver
       if ($writeable && $cal->readonly) {
         continue;
       }
-      if ($active && !$cal->storage->is_active()) {
+      if ($active && !$cal->is_active()) {
         continue;
       }
       if ($personal && $cal->get_namespace() != 'personal') {
diff --git a/plugins/calendar/drivers/kolab/kolab_user_calendar.php b/plugins/calendar/drivers/kolab/kolab_user_calendar.php
index b23d134..3add82a 100644
--- a/plugins/calendar/drivers/kolab/kolab_user_calendar.php
+++ b/plugins/calendar/drivers/kolab/kolab_user_calendar.php
@@ -27,7 +27,6 @@ class kolab_user_calendar extends kolab_calendar
   public $ready = false;
   public $readonly = true;
   public $attachments = false;
-  public $name;
 
   protected $userdata = array();
 
@@ -42,10 +41,10 @@ class kolab_user_calendar extends kolab_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);
+      $this->storage = new kolab_storage_folder_user($this->userdata['kolabtargetfolder'], '', $this->userdata);
     }
     else {  // get user record from LDAP
-      $this->storage = new kolab_storage_user_folder($user_or_folder);
+      $this->storage = new kolab_storage_folder_user($user_or_folder);
       $this->userdata = $this->storage->ldaprec;
     }
 
diff --git a/plugins/libkolab/lib/kolab_storage.php b/plugins/libkolab/lib/kolab_storage.php
index 17c4912..f10b7fe 100644
--- a/plugins/libkolab/lib/kolab_storage.php
+++ b/plugins/libkolab/lib/kolab_storage.php
@@ -887,7 +887,7 @@ class kolab_storage
         $_folders = array();
         $delim    = self::$imap->get_hierarchy_delimiter();
         $other_ns = rtrim(self::namespace_root('other'), $delim);
-        $tree     = new kolab_storage_virtual_folder('', '<root>', '');  // create tree root
+        $tree     = new kolab_storage_folder_virtual('', '<root>', '');  // create tree root
         $refs     = array('' => $tree);
 
         foreach ($folders as $idx => $folder) {
@@ -913,11 +913,11 @@ class kolab_storage
                             $refs[$parent]->parent = $parent_parent;
                         }
                         else if ($parent_parent == $other_ns) {
-                            $refs[$parent] = new kolab_storage_user_folder($parent, $parent_parent);
+                            $refs[$parent] = new kolab_storage_folder_user($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);
+                            $refs[$parent] = new kolab_storage_folder_virtual($parent, $name, $folder->get_namespace(), $parent_parent);
                         }
                         $parents[] = $refs[$parent];
                     }
@@ -1376,7 +1376,7 @@ class kolab_storage
      *
      * @param boolean Enable to return subscribed folders only (null to use configured subscription mode)
      *
-     * @return array List of kolab_storage_user_folder objects
+     * @return array List of kolab_storage_folder_user objects
      */
     public static function get_user_folders($subscribed)
     {
@@ -1393,7 +1393,7 @@ class kolab_storage
                 $foldername = join($delimiter, array_slice($path, 0, $path_len + 1));
 
                 if (!$folders[$foldername]) {
-                    $folders[$foldername] = new kolab_storage_user_folder($foldername, $other_ns);
+                    $folders[$foldername] = new kolab_storage_folder_user($foldername, $other_ns);
                 }
             }
         }
diff --git a/plugins/libkolab/lib/kolab_storage_folder.php b/plugins/libkolab/lib/kolab_storage_folder.php
index 39d1964..db1a761 100644
--- a/plugins/libkolab/lib/kolab_storage_folder.php
+++ b/plugins/libkolab/lib/kolab_storage_folder.php
@@ -22,50 +22,15 @@
  * 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_folder
+class kolab_storage_folder extends kolab_storage_folder_api
 {
     /**
-     * Folder identifier
-     * @var string
-     */
-    public $id;
-
-    /**
-     * The folder name.
-     * @var string
-     */
-    public $name;
-
-    /**
-     * The type of this folder.
-     * @var string
-     */
-    public $type;
-
-    /**
-     * Is this folder set to be the default for its type
-     * @var boolean
-     */
-    public $default = false;
-
-    /**
      * The kolab_storage_cache instance for caching operations
      * @var object
      */
     public $cache;
-    
-    /**
-     * List of direct child folders
-     * @var array
-     */
-    public $children = array();
 
     private $type_annotation;
-    private $namespace;
-    private $imap;
-    private $info;
-    private $idata;
-    private $owner;
     private $resource_uri;
 
 
@@ -74,7 +39,7 @@ class kolab_storage_folder
      */
     function __construct($name, $type = null)
     {
-        $this->imap = rcube::get_instance()->get_storage();
+        parent::__construct($name);
         $this->imap->set_options(array('skip_deleted' => true));
         $this->set_folder($name, $type);
     }
@@ -105,160 +70,6 @@ class kolab_storage_folder
         $this->cache->set_folder($this);
     }
 
-    /**
-     *
-     */
-    public function get_folder_info()
-    {
-        if (!isset($this->info))
-            $this->info = $this->imap->folder_info($this->name);
-
-        return $this->info;
-    }
-
-    /**
-     * Make IMAP folder data available for this folder
-     */
-    public function get_imap_data()
-    {
-        if (!isset($this->idata))
-            $this->idata = $this->imap->folder_data($this->name);
-
-        return $this->idata;
-    }
-
-    /**
-     * Returns IMAP metadata/annotations (GETMETADATA/GETANNOTATION)
-     *
-     * @param array List of metadata keys to read
-     * @return array Metadata entry-value hash array on success, NULL on error
-     */
-    public function get_metadata($keys)
-    {
-        $metadata = $this->imap->get_metadata($this->name, (array)$keys);
-        return $metadata[$this->name];
-    }
-
-
-    /**
-     * Sets IMAP metadata/annotations (SETMETADATA/SETANNOTATION)
-     *
-     * @param array  $entries Entry-value array (use NULL value as NIL)
-     * @return boolean True on success, False on failure
-     */
-    public function set_metadata($entries)
-    {
-        return $this->imap->set_metadata($this->name, $entries);
-    }
-
-
-    /**
-     * Returns the owner of the folder.
-     *
-     * @return string  The owner of this folder.
-     */
-    public function get_owner()
-    {
-        // return cached value
-        if (isset($this->owner))
-            return $this->owner;
-
-        $info = $this->get_folder_info();
-        $rcmail = rcube::get_instance();
-
-        switch ($info['namespace']) {
-        case 'personal':
-            $this->owner = $rcmail->get_user_name();
-            break;
-
-        case 'shared':
-            $this->owner = 'anonymous';
-            break;
-
-        default:
-            list($prefix, $user) = explode($this->imap->get_hierarchy_delimiter(), $info['name']);
-            if (strpos($user, '@') === false) {
-                $domain = strstr($rcmail->get_user_name(), '@');
-                if (!empty($domain))
-                    $user .= $domain;
-            }
-            $this->owner = $user;
-            break;
-        }
-
-        return $this->owner;
-    }
-
-
-    /**
-     * 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()
-    {
-        if (!isset($this->namespace))
-            $this->namespace = $this->imap->folder_namespace($this->name);
-        return $this->namespace;
-    }
-
-
-    /**
-     * Get IMAP ACL information for this folder
-     *
-     * @return string  Permissions as string
-     */
-    public function get_myrights()
-    {
-        $rights = $this->info['rights'];
-
-        if (!is_array($rights))
-            $rights = $this->imap->my_rights($this->name);
-
-        return join('', (array)$rights);
-    }
-
-
-    /**
-     * Get the display name value of this folder
-     *
-     * @return string Folder name
-     */
-    public function get_name()
-    {
-        return kolab_storage::object_name($this->name, $this->namespace);
-    }
-
-
-    /**
-     * 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)
-    {
-        // color is defined in folder METADATA
-        $metadata = $this->get_metadata(array(kolab_storage::COLOR_KEY_PRIVATE, kolab_storage::COLOR_KEY_SHARED));
-        if (($color = $metadata[kolab_storage::COLOR_KEY_PRIVATE]) || ($color = $metadata[kolab_storage::COLOR_KEY_SHARED])) {
-            return $color;
-        }
-
-        return $default;
-    }
-
 
     /**
      * Compose a unique resource URI for this IMAP folder
diff --git a/plugins/libkolab/lib/kolab_storage_folder_api.php b/plugins/libkolab/lib/kolab_storage_folder_api.php
new file mode 100644
index 0000000..a2d40b1
--- /dev/null
+++ b/plugins/libkolab/lib/kolab_storage_folder_api.php
@@ -0,0 +1,280 @@
+<?php
+
+/**
+ * Abstract interface class for Kolab storage IMAP folder objects
+ *
+ * @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/>.
+ */
+abstract class kolab_storage_folder_api
+{
+    /**
+     * Folder identifier
+     * @var string
+     */
+    public $id;
+
+    /**
+     * The folder name.
+     * @var string
+     */
+    public $name;
+
+    /**
+     * The type of this folder.
+     * @var string
+     */
+    public $type;
+
+    /**
+     * Is this folder set to be the default for its type
+     * @var boolean
+     */
+    public $default = false;
+
+    /**
+     * List of direct child folders
+     * @var array
+     */
+    public $children = array();
+    
+    /**
+     * Name of the parent folder
+     * @var string
+     */
+    public $parent = '';
+
+    protected $imap;
+    protected $owner;
+    protected $info;
+    protected $idata;
+    protected $namespace;
+
+
+    /**
+     * Private constructor
+     */
+    protected function __construct($name)
+    {
+      $this->name = $name;
+      $this->id   = kolab_storage::folder_id($name);
+      $this->imap = rcube::get_instance()->get_storage();
+    }
+
+
+    /**
+     * Returns the owner of the folder.
+     *
+     * @return string  The owner of this folder.
+     */
+    public function get_owner()
+    {
+        // return cached value
+        if (isset($this->owner))
+            return $this->owner;
+
+        $info = $this->get_folder_info();
+        $rcmail = rcube::get_instance();
+
+        switch ($info['namespace']) {
+        case 'personal':
+            $this->owner = $rcmail->get_user_name();
+            break;
+
+        case 'shared':
+            $this->owner = 'anonymous';
+            break;
+
+        default:
+            list($prefix, $user) = explode($this->imap->get_hierarchy_delimiter(), $info['name']);
+            if (strpos($user, '@') === false) {
+                $domain = strstr($rcmail->get_user_name(), '@');
+                if (!empty($domain))
+                    $user .= $domain;
+            }
+            $this->owner = $user;
+            break;
+        }
+
+        return $this->owner;
+    }
+
+
+    /**
+     * 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()
+    {
+        if (!isset($this->namespace))
+            $this->namespace = $this->imap->folder_namespace($this->name);
+        return $this->namespace;
+    }
+
+
+    /**
+     * Get the display name value of this folder
+     *
+     * @return string Folder name
+     */
+    public function get_name()
+    {
+        return kolab_storage::object_name($this->name, $this->namespace);
+    }
+
+
+    /**
+     * 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)
+    {
+        // color is defined in folder METADATA
+        $metadata = $this->get_metadata(array(kolab_storage::COLOR_KEY_PRIVATE, kolab_storage::COLOR_KEY_SHARED));
+        if (($color = $metadata[kolab_storage::COLOR_KEY_PRIVATE]) || ($color = $metadata[kolab_storage::COLOR_KEY_SHARED])) {
+            return $color;
+        }
+
+        return $default;
+    }
+
+
+    /**
+     * Returns IMAP metadata/annotations (GETMETADATA/GETANNOTATION)
+     *
+     * @param array List of metadata keys to read
+     * @return array Metadata entry-value hash array on success, NULL on error
+     */
+    public function get_metadata($keys)
+    {
+        $metadata = rcube::get_instance()->get_storage()->get_metadata($this->name, (array)$keys);
+        return $metadata[$this->name];
+    }
+
+
+    /**
+     * Sets IMAP metadata/annotations (SETMETADATA/SETANNOTATION)
+     *
+     * @param array  $entries Entry-value array (use NULL value as NIL)
+     * @return boolean True on success, False on failure
+     */
+    public function set_metadata($entries)
+    {
+        return $this->imap->set_metadata($this->name, $entries);
+    }
+
+
+    /**
+     *
+     */
+    public function get_folder_info()
+    {
+        if (!isset($this->info))
+            $this->info = $this->imap->folder_info($this->name);
+
+        return $this->info;
+    }
+
+    /**
+     * Make IMAP folder data available for this folder
+     */
+    public function get_imap_data()
+    {
+        if (!isset($this->idata))
+            $this->idata = $this->imap->folder_data($this->name);
+
+        return $this->idata;
+    }
+
+
+    /**
+     * Get IMAP ACL information for this folder
+     *
+     * @return string  Permissions as string
+     */
+    public function get_myrights()
+    {
+        $rights = $this->info['rights'];
+
+        if (!is_array($rights))
+            $rights = $this->imap->my_rights($this->name);
+
+        return join('', (array)$rights);
+    }
+
+
+    /**
+     * 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);
+    }
+
+    /**
+     * 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) : kolab_storage::folder_unsubscribe($this->name);
+    }
+
+}
+
diff --git a/plugins/libkolab/lib/kolab_storage_folder_user.php b/plugins/libkolab/lib/kolab_storage_folder_user.php
new file mode 100644
index 0000000..70ded87
--- /dev/null
+++ b/plugins/libkolab/lib/kolab_storage_folder_user.php
@@ -0,0 +1,101 @@
+<?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_folder_user extends kolab_storage_folder_virtual
+{
+    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 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_folder_virtual.php b/plugins/libkolab/lib/kolab_storage_folder_virtual.php
new file mode 100644
index 0000000..8b85ad5
--- /dev/null
+++ b/plugins/libkolab/lib/kolab_storage_folder_virtual.php
@@ -0,0 +1,60 @@
+<?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_folder_virtual extends kolab_storage_folder_api
+{
+    public $virtual = true;
+
+    protected $displayname;
+
+    public function __construct($name, $dispname, $ns, $parent = '')
+    {
+        parent::__construct($name);
+
+        $this->namespace = $ns;
+        $this->parent    = $parent;
+        $this->displayname = $dispname;
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * 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
diff --git a/plugins/libkolab/lib/kolab_storage_user_folder.php b/plugins/libkolab/lib/kolab_storage_user_folder.php
deleted file mode 100644
index 55e38a0..0000000
--- a/plugins/libkolab/lib/kolab_storage_user_folder.php
+++ /dev/null
@@ -1,123 +0,0 @@
-<?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
deleted file mode 100644
index 61d0fe0..0000000
--- a/plugins/libkolab/lib/kolab_storage_virtual_folder.php
+++ /dev/null
@@ -1,86 +0,0 @@
-<?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 715b2b790a39ab293ba58d8b214cf4fb7faed661
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Thu May 15 11:57:54 2014 +0200

    Fix listing of other user's calendars and sub-folders

diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js
index a1a60b1..38a32a8 100644
--- a/plugins/calendar/calendar_ui.js
+++ b/plugins/calendar/calendar_ui.js
@@ -2705,7 +2705,7 @@ function rcube_calendar_ui(settings)
       // insert to #calendar-select options if writeable
       select = $('#edit-calendar');
       if (fc && !cal.readonly && select.length && !select.find('option[value="'+id+'"]').length) {
-        $('<option>').attr('value', id).text(Q(cal.name)).appendTo(select);
+        $('<option>').attr('value', id).html(cal.name).appendTo(select);
       }
     }
 
@@ -2751,7 +2751,6 @@ function rcube_calendar_ui(settings)
     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
diff --git a/plugins/calendar/drivers/kolab/kolab_calendar.php b/plugins/calendar/drivers/kolab/kolab_calendar.php
index 97f6a96..374ab5d 100644
--- a/plugins/calendar/drivers/kolab/kolab_calendar.php
+++ b/plugins/calendar/drivers/kolab/kolab_calendar.php
@@ -33,6 +33,8 @@ class kolab_calendar
   public $alarms = false;
   public $categories = array();
   public $storage;
+
+  public $type = 'event';
   public $name;
 
   protected $cal;
@@ -198,6 +200,25 @@ class kolab_calendar
 
 
   /**
+   * Update properties of this calendar folder
+   *
+   * @see calendar_driver::edit_calendar()
+   */
+  public function update(&$prop)
+  {
+    $prop['oldname'] = $this->get_realname();
+    $newfolder = kolab_storage::folder_update($prop);
+
+    if ($newfolder === false) {
+      $this->cal->last_error = $this->cal->gettext(kolab_storage::$last_error);
+      return false;
+    }
+
+    // create ID
+    return kolab_storage::folder_id($newfolder);
+  }
+
+  /**
    * Getter for a single event object
    */
   public function get_event($id)
diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php
index 12e4258..13c460e 100644
--- a/plugins/calendar/drivers/kolab/kolab_driver.php
+++ b/plugins/calendar/drivers/kolab/kolab_driver.php
@@ -129,7 +129,13 @@ class kolab_driver extends calendar_driver
       $listname = $cal->get_foldername();
       $imap_path = explode('/', $cal->name);
       $topname = array_pop($imap_path);
-      $parent_id = kolab_storage::folder_id(join('/', $imap_path), true);
+      $parent_id = kolab_storage::folder_id(join('/', $imap_path));
+
+      // turn a kolab_storage_folder object into a kolab_calendar
+      if ($cal instanceof kolab_storage_folder) {
+          $cal = new kolab_calendar($cal->name, $this->cal);
+          $this->calendars[$cal->id] = $cal;
+      }
 
       // special handling for user or virtual folders
       if ($cal instanceof kolab_storage_user_folder) {
@@ -141,7 +147,7 @@ class kolab_driver extends calendar_driver
           'color'    => $cal->get_color(),
           'active'   => $cal->is_active(),
           'owner'    => $cal->get_owner(),
-          'virtual' => false,
+          'virtual'  => false,
           'readonly' => true,
           'class_name' => 'user',
         );
@@ -306,16 +312,7 @@ class kolab_driver extends calendar_driver
   public function edit_calendar($prop)
   {
     if ($prop['id'] && ($cal = $this->get_calendar($prop['id']))) {
-      $prop['oldname'] = $cal->get_realname();
-      $newfolder = kolab_storage::folder_update($prop);
-
-      if ($newfolder === false) {
-        $this->last_error = $this->cal->gettext(kolab_storage::$last_error);
-        return false;
-      }
-
-      // create ID
-      $id = kolab_storage::folder_id($newfolder);
+      $id = $cal->update($prop);
     }
     else {
       $id = $prop['id'];
diff --git a/plugins/calendar/drivers/kolab/kolab_user_calendar.php b/plugins/calendar/drivers/kolab/kolab_user_calendar.php
index a5795a4..b23d134 100644
--- a/plugins/calendar/drivers/kolab/kolab_user_calendar.php
+++ b/plugins/calendar/drivers/kolab/kolab_user_calendar.php
@@ -130,6 +130,20 @@ class kolab_user_calendar extends kolab_calendar
     return false;
   }
 
+
+  /**
+   * Update properties of this calendar folder
+   *
+   * @see calendar_driver::edit_calendar()
+   */
+  public function update(&$prop)
+  {
+    // don't change anything.
+    // let kolab_driver save props in local prefs
+    return $prop['id'];
+  }
+
+
   /**
    * Getter for a single event object
    */
diff --git a/plugins/calendar/lib/calendar_ui.php b/plugins/calendar/lib/calendar_ui.php
index a3ed443..4584f19 100644
--- a/plugins/calendar/lib/calendar_ui.php
+++ b/plugins/calendar/lib/calendar_ui.php
@@ -229,7 +229,7 @@ class calendar_ui
       if ($attrib['activeonly'] && !$prop['active'])
         continue;
 
-      $html .= html::tag('li', array('id' => 'rcmlical' . rcube_utils::html_identifier($id)),
+      $html .= html::tag('li', array('id' => 'rcmlical' . $id),
         $content = $this->calendar_list_item($id, $prop, $jsenv)
       );
     }
@@ -243,7 +243,7 @@ class calendar_ui
   /**
    * Return html for a structured list <ul> for the folder tree
    */
-  public function list_tree_html(&$node, &$data, &$jsenv, $attrib)
+  public function list_tree_html($node, $data, &$jsenv, $attrib)
   {
     $out = '';
     foreach ($node->children as $folder) {
diff --git a/plugins/calendar/localization/en_US.inc b/plugins/calendar/localization/en_US.inc
index b6e7ff2..92b4fb5 100644
--- a/plugins/calendar/localization/en_US.inc
+++ b/plugins/calendar/localization/en_US.inc
@@ -86,7 +86,7 @@ $labels['nmonthsback'] = '$nr months back';
 $labels['showurl'] = 'Show calendar URL';
 $labels['showurldescription'] = 'Use the following address to access (read only) your calendar from other applications. You can copy and paste this into any calendar software that supports the iCal format.';
 $labels['caldavurldescription'] = 'Copy this address to a <a href="http://en.wikipedia.org/wiki/CalDAV" target="_blank">CalDAV</a> client application (e.g. Evolution or Mozilla Thunderbird) to fully synchronize this specific calendar with your computer or mobile device.';
-$labels['calsearchresults'] = 'Additional Results';
+$labels['calsearchresults'] = 'Available Calendars';
 
 // agenda view
 $labels['listrange'] = 'Range to display:';
diff --git a/plugins/calendar/skins/larry/calendar.css b/plugins/calendar/skins/larry/calendar.css
index a44899b..71dd106 100644
--- a/plugins/calendar/skins/larry/calendar.css
+++ b/plugins/calendar/skins/larry/calendar.css
@@ -297,6 +297,10 @@ pre {
 	padding: 2px 8px 2px 8px;
 }
 
+#calendars .searchresults .listing li {
+	background-color: #c7e3ef;
+}
+
 #calfeedurl,
 #caldavurl {
 	width: 98%;
diff --git a/plugins/libkolab/js/folderlist.js b/plugins/libkolab/js/folderlist.js
index e2119a8..3c35846 100644
--- a/plugins/libkolab/js/folderlist.js
+++ b/plugins/libkolab/js/folderlist.js
@@ -62,39 +62,12 @@ function kolab_folderlist(node, p)
                           return;
 
                       var li = $(this).closest('li'),
-                          id = li.attr('id').replace(new RegExp('^'+p.id_prefix), ''),
+                          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,
-                          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;
-                          dom_node.children('span,a').first().html(Q(prop.listname));
-                      }
-
-                      // 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);
-                      }
+                          has_children = node.children && node.children.length;
 
-                      delete prop.html;
-                      me.triggerEvent('insert-item', { id: id, data: prop, item: li });
+                      // copy item to the main list
+                      add_result2list(id, li, true);
 
                       if (has_children) {
                           li.find('input[type=checkbox]').first().prop('disabled', true).get(0).checked = true;
@@ -127,6 +100,49 @@ function kolab_folderlist(node, p)
         }
     }
 
+    // helper method to (recursively) add a search result item to the main list widget
+    function add_result2list(id, li, active)
+    {
+        var node = search_results_widget.get_node(id),
+            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();
+
+        // find parent node and insert at the right place
+        if (parent_id && me.get_node(parent_id)) {
+            dom_node.children('span,a').first().html(Q(prop.editname));
+        }
+        else if (parent_id && search_results[parent_id]) {
+            // copy parent tree from search results
+            add_result2list(parent_id, $(search_results_widget.get_item(parent_id)), false);
+        }
+        else if (parent_id) {
+            // use full name for list display
+            dom_node.children('span,a').first().html(Q(prop.name));
+        }
+
+        // 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;
+        prop.active = active;
+        me.triggerEvent('insert-item', { id: id, data: prop, item: li });
+    }
+
     // do some magic when search is performed on the widget
     this.addEventListener('search', function(search) {
         // hide search results


commit df08826c0346683ee3dc9892fcd67b68e5dcf7c1
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Thu May 15 11:57:12 2014 +0200

    Improve listing of user folders: also list them if one has access to a child folder

diff --git a/plugins/libkolab/lib/kolab_storage.php b/plugins/libkolab/lib/kolab_storage.php
index 872ce29..17c4912 100644
--- a/plugins/libkolab/lib/kolab_storage.php
+++ b/plugins/libkolab/lib/kolab_storage.php
@@ -299,6 +299,24 @@ class kolab_storage
 
 
     /**
+     * Return the (first) path of the requested IMAP namespace
+     *
+     * @param string  Namespace name (personal, shared, other)
+     * @return string IMAP root path for that namespace
+     */
+    public static function namespace_root($name)
+    {
+        foreach ((array)self::$imap->get_namespace($name) as $paths) {
+            if (strlen($paths[0]) > 1) {
+                return $paths[0];
+            }
+        }
+
+        return '/';
+    }
+
+
+    /**
      * Deletes IMAP folder
      *
      * @param string $name Folder name (UTF7-IMAP)
@@ -868,14 +886,10 @@ class kolab_storage
     {
         $_folders = array();
         $delim    = self::$imap->get_hierarchy_delimiter();
-        $other_ns = self::$imap->get_namespace('other');
+        $other_ns = rtrim(self::namespace_root('other'), $delim);
         $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);
@@ -894,7 +908,11 @@ class kolab_storage
                     array_pop($path);
                     $parent_parent = join($delim, $path);
                     if (!$refs[$parent]) {
-                        if ($parent_parent == $other_ns) {
+                        if ($folder->type && self::folder_type($parent) == $folder->type) {
+                            $refs[$parent] = new kolab_storage_folder($parent, $folder->type);
+                            $refs[$parent]->parent = $parent_parent;
+                        }
+                        else if ($parent_parent == $other_ns) {
                             $refs[$parent] = new kolab_storage_user_folder($parent, $parent_parent);
                         }
                         else {
@@ -1314,12 +1332,11 @@ class kolab_storage
         $results = self::$ldap->search(array('cn','mail','alias'), $query, $mode, $required, $limit);
 
         // resolve to IMAP folder name
-        $other_ns = self::$imap->get_namespace('other');
+        $root = self::namespace_root('other');
         $user_attrib = rcube::get_instance()->config->get('kolab_auth_login', 'mail');
 
-        array_walk($results, function(&$user, $dn) use ($other_ns, $user_attrib) {
+        array_walk($results, function(&$user, $dn) use ($root, $user_attrib) {
             list($localpart, $domain) = explode('@', $user[$user_attrib]);
-            $root = $other_ns[0][0];
             $user['kolabtargetfolder'] = $root . $localpart;
         });
 
@@ -1346,11 +1363,7 @@ class kolab_storage
         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];
-            }
-
+            $other_ns = self::namespace_root('other');
             $folders = self::list_folders($other_ns . $mbox, '*', $type, $subscribed, $folderdata);
         }
 
@@ -1363,7 +1376,7 @@ class kolab_storage
      *
      * @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)
+     * @return array List of kolab_storage_user_folder objects
      */
     public static function get_user_folders($subscribed)
     {
@@ -1371,19 +1384,15 @@ class kolab_storage
 
         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));
-            }
+            $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) {
+                // truncate folder path to top-level folders of the 'other' namespace
                 $path = explode($delimiter, $foldername);
-                $depth = count($path) - $other_depth;
-                array_pop($path);
+                $foldername = join($delimiter, array_slice($path, 0, $path_len + 1));
 
-                // only list top-level folders of the 'other' namespace
-                if ($depth == 1) {
+                if (!$folders[$foldername]) {
                     $folders[$foldername] = new kolab_storage_user_folder($foldername, $other_ns);
                 }
             }
diff --git a/plugins/libkolab/lib/kolab_storage_folder.php b/plugins/libkolab/lib/kolab_storage_folder.php
index 7db493d..39d1964 100644
--- a/plugins/libkolab/lib/kolab_storage_folder.php
+++ b/plugins/libkolab/lib/kolab_storage_folder.php
@@ -25,6 +25,12 @@
 class kolab_storage_folder
 {
     /**
+     * Folder identifier
+     * @var string
+     */
+    public $id;
+
+    /**
      * The folder name.
      * @var string
      */
@@ -89,6 +95,7 @@ class kolab_storage_folder
         $this->default      = $suffix == 'default';
         $this->name         = $name;
         $this->resource_uri = null;
+        $this->id           = kolab_storage::folder_id($name);
 
         // get a new cache instance of folder type changed
         if (!$this->cache || $type != $oldtype)




More information about the commits mailing list