From ec2185a1cb2a0d412ab9e916618972bc4c70f68c Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Tue, 05 Feb 2013 10:12:37 -0500
Subject: [PATCH] Add special config attribute 'group_filters' to specify a fixed list of groups to be listed in the leftmost pane, each having individual base-dn/filter parameters for contact listing

---
 program/include/rcmail.php           |    2 
 program/lib/Roundcube/rcube_ldap.php |  244 ++++++++++++++++++++++++++++++------------------
 2 files changed, 153 insertions(+), 93 deletions(-)

diff --git a/program/include/rcmail.php b/program/include/rcmail.php
index 70dba41..a149db1 100644
--- a/program/include/rcmail.php
+++ b/program/include/rcmail.php
@@ -287,7 +287,7 @@
         $list[$id] = array(
           'id'       => $id,
           'name'     => html::quote($prop['name']),
-          'groups'   => is_array($prop['groups']),
+          'groups'   => !empty($prop['groups']) || !empty($prop['group_filters']),
           'readonly' => !$prop['writable'],
           'hidden'   => $prop['hidden'],
           'autocomplete' => in_array($id, $autocomplete)
diff --git a/program/lib/Roundcube/rcube_ldap.php b/program/lib/Roundcube/rcube_ldap.php
index 80b85d4..e54e659 100644
--- a/program/lib/Roundcube/rcube_ldap.php
+++ b/program/lib/Roundcube/rcube_ldap.php
@@ -38,7 +38,7 @@
     /** private properties */
     protected $conn;
     protected $prop = array();
-    protected $fieldmap = array();
+    protected $fieldmap = array('_objclass' => 'objectclass');
     protected $sub_filter;
     protected $filter = '';
     protected $result = null;
@@ -82,6 +82,12 @@
                 $this->prop['groups']['name_attr'] = 'cn';
             if (empty($this->prop['groups']['scope']))
                 $this->prop['groups']['scope'] = 'sub';
+
+            // add group name attrib to fieldmap in order to have it fetched
+            $this->fieldmap['_groupname'] = $this->prop['groups']['name_attr'];
+        }
+        else if (is_array($p['group_filters']) && count($p['group_filters'])) {
+            $this->groups = true;
         }
 
         // fieldmap property is given
@@ -549,13 +555,15 @@
         }
         else
         {
+            $prop = $this->group_id ? $this->group_data : $this->prop;
+
             // add general filter to query
-            if (!empty($this->prop['filter']) && empty($this->filter))
-                $this->set_search_set($this->prop['filter']);
+            if (!empty($prop['filter']) && empty($this->filter))
+                $this->set_search_set($prop['filter']);
 
             // exec LDAP search if no result resource is stored
             if ($this->conn && !$this->ldap_result)
-                $this->_exec_search();
+                $this->_exec_search($prop);
 
             // count contacts for this user
             $this->result = $this->count();
@@ -564,7 +572,7 @@
             if ($this->ldap_result && $this->result->count > 0)
             {
                 // sorting still on the ldap server
-                if ($this->sort_col && $this->prop['scope'] !== 'base' && !$this->vlv_active)
+                if ($this->sort_col && $prop['scope'] !== 'base' && !$this->vlv_active)
                     ldap_sort($this->conn, $this->ldap_result, $this->sort_col);
 
                 // get all entries from the ldap server
@@ -599,6 +607,7 @@
 
         // fetch group object
         if (empty($entries)) {
+            $this->_debug("C: Read Group [dn: $dn]");
             $result = @ldap_read($this->conn, $dn, '(objectClass=*)', array('dn','objectClass','member','uniqueMember','memberURL'));
             if ($result === false)
             {
@@ -657,21 +666,6 @@
         if (empty($entry[$attr]))
             return $group_members;
 
-        // add group record to cache if it isn't yet there
-        $group_id = self::dn_encode($dn);
-        $group_cache = $this->cache->get('groups');
-        if (!$group_cache[$group_id]) {
-            $name_attr = $this->prop['groups']['name_attr'];
-            $group_name = is_array($ldap_data[$i][$name_attr]) ? $ldap_data[$i][$name_attr][0] : $ldap_data[$i][$name_attr];
-
-            $group_cache[$group_id]['ID'] = $group_id;
-            $group_cache[$group_id]['dn'] = $dn;
-            $group_cache[$group_id]['name'] = $group_name;
-            $group_cache[$group_id]['member_attr'] = $this->get_group_member_attr($entry[$i]['objectclass']);
-
-            $this->cache->set('groups', $group_cache);
-        }
-
         // read these attributes for all members
         $attrib = $count ? array('dn') : array_values($this->fieldmap);
         $attrib[] = 'objectClass';
@@ -726,26 +720,15 @@
 
             // add search filter if any
             $filter = $this->filter ? '(&(' . $m[3] . ')(' . $this->filter . '))' : $m[3];
-            $func = $m[2] == 'sub' ? 'ldap_search' : ($m[2] == 'base' ? 'ldap_read' : 'ldap_list');
-
-            $attrib = $count ? array('dn') : array_values($this->fieldmap);
-            if ($result = @$func($this->conn, $m[1], $filter,
-                $attrib, 0, (int)$this->prop['sizelimit'], (int)$this->prop['timelimit'])
-            ) {
-                $this->_debug("S: ".ldap_count_entries($this->conn, $result)." record(s) for ".$m[1]);
-            }
-            else {
-                $this->_debug("S: ".ldap_error($this->conn));
-                return $group_members;
-            }
-
-            $entries = @ldap_get_entries($this->conn, $result);
-            for ($j = 0; $j < $entries['count']; $j++)
-            {
-                if ($nested_group_members = $this->list_group_members($entries[$j]['dn'], $count))
-                    $group_members = array_merge($group_members, $nested_group_members);
-                else
-                    $group_members[] = $entries[$j];
+            $attrs = $count ? array('dn') : array_values($this->fieldmap);
+            if ($result = $this->ldap_search($m[1], $filter, $m[2], $attrs, $this->group_data)) {
+                $entries = @ldap_get_entries($this->conn, $result);
+                for ($j = 0; $j < $entries['count']; $j++) {
+                    if ($nested_group_members = $this->list_group_members($entries[$j]['dn'], $count))
+                        $group_members = array_merge($group_members, $nested_group_members);
+                    else
+                        $group_members[] = $entries[$j];
+                }
             }
         }
 
@@ -910,7 +893,7 @@
 
         // set filter string and execute search
         $this->set_search_set($filter);
-        $this->_exec_search();
+        $this->_exec_search($this->prop);
 
         if ($select)
             $this->list_records();
@@ -936,13 +919,15 @@
             $count = count($this->list_group_members($this->group_data['dn'], true));
         }
         else if ($this->conn) {
+            $prop = $this->group_id ? $this->group_data : $this->prop;
+
             // We have a connection but no result set, attempt to get one.
             if (empty($this->filter)) {
                 // The filter is not set, set it.
                 $this->filter = $this->prop['filter'];
             }
 
-            $count = (int) $this->_exec_search(true);
+            $count = (int) $this->_exec_search($prop, true);
         }
 
         return new rcube_result_set($count, ($this->list_page-1) * $this->page_size);
@@ -1434,57 +1419,35 @@
     /**
      * Execute the LDAP search based on the stored credentials
      */
-    private function _exec_search($count = false)
+    private function _exec_search($prop, $count = false)
     {
         if ($this->ready)
         {
-            $filter = $this->filter ? $this->filter : '(objectclass=*)';
-            $function = $this->_scope2func($this->prop['scope'], $ns_function);
-
-            $this->_debug("C: Search [$filter][dn: $this->base_dn]");
+            $function = $this->_scope2func($prop['scope'], $ns_function);
+            $base_dn = $this->group_id && $prop['base_dn'] ? $prop['base_dn'] : $this->base_dn;
 
             // when using VLV, we get the total count by...
-            if (!$count && $function != 'ldap_read' && $this->prop['vlv'] && !$this->group_id) {
+            if (!$count && $function != 'ldap_read' && $prop['vlv']) {
                 // ...either reading numSubOrdinates attribute
-                if ($this->prop['numsub_filter'] && ($result_count = @$ns_function($this->conn, $this->base_dn, $this->prop['numsub_filter'], array('numSubOrdinates'), 0, 0, 0))) {
+                if ($prop['numsub_filter'] && ($result_count = @$ns_function($this->conn, $base_dn, $prop['numsub_filter'], array('numSubOrdinates'), 0, 0, 0))) {
                     $counts = ldap_get_entries($this->conn, $result_count);
                     for ($this->vlv_count = $j = 0; $j < $counts['count']; $j++)
                         $this->vlv_count += $counts[$j]['numsubordinates'][0];
                     $this->_debug("D: total numsubordinates = " . $this->vlv_count);
                 }
                 else if (!function_exists('ldap_parse_virtuallist_control'))  // ...or by fetching all records dn and count them
-                    $this->vlv_count = $this->_exec_search(true);
-
-                $this->vlv_active = $this->_vlv_set_controls($this->prop, $this->list_page, $this->page_size);
+                    $this->vlv_count = $this->_exec_search($prop, true);
             }
 
-            // only fetch dn for count (should keep the payload low)
-            $attrs = $count ? array('dn') : array_values($this->fieldmap);
-            if ($this->ldap_result = @$function($this->conn, $this->base_dn, $filter,
-                $attrs, 0, (int)$this->prop['sizelimit'], (int)$this->prop['timelimit'])
-            ) {
-                // when running on a patched PHP we can use the extended functions to retrieve the total count from the LDAP search result
-                if ($this->vlv_active && function_exists('ldap_parse_virtuallist_control')) {
-                    if (ldap_parse_result($this->conn, $this->ldap_result,
-                        $errcode, $matcheddn, $errmsg, $referrals, $serverctrls)
-                        && $serverctrls // can be null e.g. in case of adm. limit error
-                    ) {
-                        ldap_parse_virtuallist_control($this->conn, $serverctrls,
-                            $last_offset, $this->vlv_count, $vresult);
-                        $this->_debug("S: VLV result: last_offset=$last_offset; content_count=$this->vlv_count");
-                    }
-                    else {
-                        $this->_debug("S: ".($errmsg ? $errmsg : ldap_error($this->conn)));
-                    }
+            $filter = $this->filter ? $this->filter : '(objectclass=*)';
+            $attrs = $count ? array('dn') : array_values($this->fieldmap);  // only fetch dn for count (should keep the payload low)
+            if ($this->ldap_result = $this->ldap_search($base_dn, $filter, $prop['scope'], $attrs, $prop)) {
+                if ($count) {
+                    return ldap_count_entries($this->conn, $this->ldap_result);
                 }
-
-                $entries_count = ldap_count_entries($this->conn, $this->ldap_result);
-                $this->_debug("S: $entries_count record(s)");
-
-                return $count ? $entries_count : true;
-            }
-            else {
-                $this->_debug("S: ".ldap_error($this->conn));
+                else {
+                    return true;
+                }
             }
         }
 
@@ -1540,20 +1503,19 @@
     private function _ldap2result($rec)
     {
         $out = array('_type' => 'person');
+        $fieldmap = $this->fieldmap;
 
         if ($rec['dn'])
             $out[$this->primary_key] = self::dn_encode($rec['dn']);
 
         // determine record type
-        if (array_intersect(array('groupofuniquenames','kolabgroupofuniquenames'), (array)$rec['objectclass'])) {
+        if (array_intersect(array('groupofuniquenames','kolabgroupofuniquenames'), array_map('strtolower', (array)$rec['objectclass']))) {
             $out['_type'] = 'group';
             $out['readonly'] = true;
-            if ($this->fieldmap['groupname']) {
-                $this->fieldmap['name'] = $this->fieldmap['groupname'];
-            }
+            $fieldmap['name'] = $fieldmap['_groupname'];
         }
 
-        foreach ($this->fieldmap as $rf => $lf)
+        foreach ($fieldmap as $rf => $lf)
         {
             for ($i=0; $i < $rec[$lf]['count']; $i++) {
                 if (!($value = $rec[$lf][$i]))
@@ -1718,20 +1680,14 @@
 
     /**
      * Setter for the current group
-     * (empty, has to be re-implemented by extending class)
      */
     function set_group($group_id)
     {
-        if ($group_id)
-        {
-            if (($group_cache = $this->cache->get('groups')) === null)
-                $group_cache = $this->_fetch_groups();
-
+        if ($group_id) {
             $this->group_id = $group_id;
-            $this->group_data = $group_cache[$group_id];
+            $this->group_data = $this->get_group_entry($group_id);
         }
-        else
-        {
+        else {
             $this->group_id = 0;
             $this->group_data = null;
         }
@@ -1772,6 +1728,23 @@
      */
     private function _fetch_groups($vlv_page = 0)
     {
+        // special case: list groups from 'group_filters' config
+        if (!empty($this->prop['group_filters'])) {
+            $groups = array();
+
+            // list regular groups configuration as special filter
+            if (!empty($this->prop['groups']['filter'])) {
+                $id = '__groups__';
+                $groups[$id] = array('ID' => $id, 'name' => rcube_label('groups')) + $this->prop['groups'];
+            }
+
+            foreach ($this->prop['group_filters'] as $id => $prop) {
+                $groups[$id] = $prop + array('ID' => $id, 'name' => ucfirst($id));
+            }
+
+            return $groups;
+        }
+
         $base_dn = $this->groups_base_dn;
         $filter = $this->prop['groups']['filter'];
         $name_attr = $this->prop['groups']['name_attr'];
@@ -1841,6 +1814,40 @@
         $this->cache->set('groups', $groups);
 
         return $groups;
+    }
+
+    /**
+     * Fetch a group entry from LDAP and save in local cache
+     */
+    private function get_group_entry($group_id)
+    {
+        if (($group_cache = $this->cache->get('groups')) === null)
+            $group_cache = $this->_fetch_groups();
+
+        // add group record to cache if it isn't yet there
+        if (!isset($group_cache[$group_id])) {
+            $name_attr = $this->prop['groups']['name_attr'];
+            $dn = self::dn_decode($group_id);
+
+            $this->_debug("C: Read Group [dn: $dn]");
+            if ($result = @ldap_read($this->conn, $dn, '(objectClass=*)', array('dn','objectClass','member','uniqueMember','memberURL',$name_attr))) {
+                $list = ldap_get_entries($this->conn, $result);
+                $entry = $list[0];
+                $group_name = is_array($entry[$name_attr]) ? $entry[$name_attr][0] : $entry[$name_attr];
+                $group_cache[$group_id]['ID'] = $group_id;
+                $group_cache[$group_id]['dn'] = $dn;
+                $group_cache[$group_id]['name'] = $group_name;
+                $group_cache[$group_id]['member_attr'] = $this->get_group_member_attr($entry['objectclass']);
+            }
+            else {
+                $this->_debug("S: ".ldap_error($this->conn));
+                $group_cache[$group_id] = false;
+            }
+
+            $this->cache->set('groups', $group_cache);
+        }
+
+        return $group_cache[$group_id];
     }
 
     /**
@@ -2246,6 +2253,59 @@
     }
 
     /**
+     * Wrapper for ldap_search or ldap_list depending on the given scope.
+     * It optionally uses VLV index if configured in $prop
+     *
+     * @param string Base DN to read from
+     * @param string Query filter to use
+     * @param string Listing scope (sub|list|base)
+     * @param array List of entry attributes to read
+     * @param array Hash array with query configuration properties:
+     *   - vlv: true if VLV index should be used
+     *   - sort: array of sort attributes (has to be in sync with the VLV index)
+     */
+    protected function ldap_search($base_dn, $filter = '(objectclass=*)', $scope = 'sub', $attrs = array('dn'), $prop = array())
+    {
+        if ($this->ready) {
+            $this->_debug("C: LDAP Search [$filter][dn: $base_dn]");
+
+            // set VLV controls if requested
+            if ($prop['vlv'] && $scope != 'base')
+                $this->vlv_active = $this->_vlv_set_controls($prop, $this->list_page, $this->page_size);
+
+            $function = $this->_scope2func($scope);
+            if ($ldap_result = $function($this->conn, $base_dn, $filter,
+                $attrs, 0, (int)$this->prop['sizelimit'], (int)$this->prop['timelimit'])
+            ) {
+                // when running on a patched PHP we can use the extended functions to retrieve the total count from the LDAP search result
+                if ($this->vlv_active && function_exists('ldap_parse_virtuallist_control')) {
+                    if (ldap_parse_result($this->conn, $this->ldap_result,
+                        $errcode, $matcheddn, $errmsg, $referrals, $serverctrls)
+                        && $serverctrls // can be null e.g. in case of adm. limit error
+                    ) {
+                        ldap_parse_virtuallist_control($this->conn, $serverctrls,
+                            $last_offset, $this->vlv_count, $vresult);
+                        $this->_debug("S: VLV result: last_offset=$last_offset; content_count=$this->vlv_count");
+                    }
+                    else {
+                        $this->_debug("S: ".($errmsg ? $errmsg : ldap_error($this->conn)));
+                    }
+                }
+
+                $entries_count = ldap_count_entries($this->conn, $ldap_result);
+                $this->_debug("S: $entries_count record(s)");
+
+                return $ldap_result;
+            }
+            else {
+                $this->_debug("S: ".ldap_error($this->conn));
+            }
+        }
+
+        return false;
+    }
+
+    /**
      * Wrapper for ldap_add()
      */
     protected function ldap_add($dn, $entry)

--
Gitblit v1.9.1