From f1154163b0a9efb21d722bc658352739040ffd61 Mon Sep 17 00:00:00 2001
From: thomascube <thomas@roundcube.net>
Date: Sat, 28 Apr 2007 14:07:12 -0400
Subject: [PATCH] Merged branch devel-addressbook from r443 back to trunk

---
 program/include/rcube_ldap.inc |  647 +++++++++++++++++++++++++++++++++++++---------------------
 1 files changed, 412 insertions(+), 235 deletions(-)

diff --git a/program/include/rcube_ldap.inc b/program/include/rcube_ldap.inc
index 7cb9dee..06a99ad 100644
--- a/program/include/rcube_ldap.inc
+++ b/program/include/rcube_ldap.inc
@@ -5,255 +5,432 @@
  | program/include/rcube_ldap.inc                                        |
  |                                                                       |
  | This file is part of the RoundCube Webmail client                     |
- | Copyright (C) 2005, RoundCube Dev. - Switzerland                      |
+ | Copyright (C) 2006-2007, RoundCube Dev. - Switzerland                 |
  | Licensed under the GNU GPL                                            |
  |                                                                       |
  | PURPOSE:                                                              |
- |   Manage an LDAP connection                                           |
+ |   Interface to an LDAP address directory                              |
  |                                                                       |
  +-----------------------------------------------------------------------+
- | Author: Jeremy Jongsma <jeremy@jongsma.org>                           |
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
  +-----------------------------------------------------------------------+
 
  $Id$
 
 */
 
-require_once("bugs.inc");
-
 class rcube_ldap
-  {
+{
   var $conn;
-  var $host;
-  var $port;
-  var $protocol;
-  var $base_dn;
-  var $bind_dn;
-  var $bind_pass;
-
-  // PHP 5 constructor
-  function __construct()
-    {
-    }
-
-  // PHP 4 constructor
-  function rcube_ldap()
-    {
-    $this->__construct();
-    }
-
-  function connect($hosts, $port=389, $protocol=3)
-    {
-    if (!function_exists('ldap_connect'))
-      raise_error(array("type" => "ldap",
-                        "message" => "No ldap support in this installation of php."),
-                         TRUE);
-
-    if (is_resource($this->conn))
-      return TRUE;
+  var $prop = array();
+  var $fieldmap = array();
+  
+  var $filter = '';
+  var $result = null;
+  var $ldap_result = null;
+  var $sort_col = '';
+  
+  /** public properties */
+  var $primary_key = 'ID';
+  var $readonly = true;
+  var $list_page = 1;
+  var $page_size = 10;
+  var $ready = false;
+  
+  
+  /**
+   * Object constructor
+   *
+   * @param array LDAP connection properties
+   * @param integer User-ID
+   */
+  function __construct($p)
+  {
+    $this->prop = $p;
     
-    if (!is_array($hosts))
-      $hosts = array($hosts);
-
-    foreach ($hosts as $host)
-      {
-      if ($lc = @ldap_connect($host, $port))
-        {
-        @ldap_set_option($lc, LDAP_OPT_PROTOCOL_VERSION, $protocol);
-        $this->host = $host;
-        $this->port = $port;
-        $this->protocol = $protocol;
-        $this->conn = $lc;
-        return TRUE;
-        }
-      }
+    foreach ($p as $prop => $value)
+      if (preg_match('/^(.+)_field$/', $prop, $matches))
+        $this->fieldmap[$matches[1]] = $value;
     
-    if (!is_resource($this->conn))
-      raise_error(array("type" => "ldap",
-                        "message" => "Could not connect to any LDAP server, tried $host:$port last"),
-                         TRUE);
-    }
-
-  function close()
-    {
-    if ($this->conn)
-      {
-      if (@ldap_unbind($this->conn))
-        return TRUE;
-      else
-        raise_error(array("code" => ldap_errno($this->conn),
-                          "type" => "ldap",
-                          "message" => "Could not close connection to LDAP server: ".ldap_error($this->conn)),
-                    TRUE);
-      }
-    return FALSE;
-    }
-
-  // Merge with connect()?
-  function bind($dn=null, $pass=null)
-    {
-    if ($this->conn)
-      {
-      if ($dn)
-        if (@ldap_bind($this->conn, $dn, $pass))
-          return TRUE;
-        else
-          raise_error(array("code" => ldap_errno($this->conn),
-                            "type" => "ldap",
-                            "message" => "Bind failed for dn=$dn: ".ldap_error($this->conn)),
-                      TRUE);
-      else
-        if (@ldap_bind($this->conn))
-          return TRUE;
-        else
-          raise_error(array("code" => ldap_errno($this->conn),
-                            "type" => "ldap",
-                            "message" => "Anonymous bind failed: ".ldap_error($this->conn)),
-                      TRUE);
-      }
-    else
-      raise_error(array("type" => "ldap",
-                        "message" => "Attempted bind on nonexistent connection"), TRUE);
-    return FALSE;
-    }
-
-  function count($base, $filter=null, $attributes=null, $scope="sub")
-    {
-    if ($this->conn)
-      {
-      if ($scope === 'sub')
-        $sr = @ldap_search($this->conn, $base, $filter, $attributes, 0, $limit);
-      else if ($scope === 'one')
-        $sr = @ldap_list($this->conn, $base, $filter, $attributes, 0, $limit);
-      else if ($scope === 'base')
-        $sr = @ldap_read($this->conn, $base, $filter, $attributes, 0, $limit);
-      if ($sr)
-        return @ldap_count_entries($this->conn, $sr);
-      }
-    else
-      raise_error(array("type" => "ldap",
-                        "message" => "Attempted count search on nonexistent connection"), TRUE);
-    return FALSE;
-    }
-
-  function search($base, $filter=null, $attributes=null, $scope='sub', $sort=null, $limit=0)
-    {
-    if ($this->conn)
-      {
-      if ($scope === 'sub')
-        $sr = @ldap_search($this->conn, $base, $filter, $attributes, 0, $limit);
-      else if ($scope === 'one')
-        $sr = @ldap_list($this->conn, $base, $filter, $attributes, 0, $limit);
-      else if ($scope === 'base')
-        $sr = @ldap_read($this->conn, $base, $filter, $attributes, 0, $limit);
-      if ($sr)
-        {
-        if ($sort && $scope !== "base")
-          {
-          if (is_array($sort))
-            {
-            // Start from the end so first sort field has highest priority
-            $sortfields = array_reverse($sort);
-            foreach ($sortfields as $sortfield)
-              @ldap_sort($this->conn, $sr, $sortfield);
-            }
-          else
-            @ldap_sort($this->conn, $sr, $sort);
-          }
-        return @ldap_get_entries($this->conn, $sr);
-        }
-      }
-    else
-      raise_error(array("type" => "ldap",
-                        "message" => "Attempted search on nonexistent connection"), TRUE);
-    return FALSE;
-    }
-
-  function add($dn, $object)
-    {
-    if ($this->conn)
-      {
-      if (@ldap_add($this->conn, $dn, $object))
-        return TRUE;
-      else
-        raise_error(array("code" => ldap_errno($this->conn),
-                          "type" => "ldap",
-                          "message" => "Add object failed: ".ldap_error($this->conn)),
-                    TRUE);
-      }
-    else
-      raise_error(array("type" => "ldap",
-                        "message" => "Add object faile: no connection"),
-                  TRUE);
-    return FALSE;
-    }
-
-  function modify($dn, $object)
-    {
-    if ($this->conn)
-      {
-      if (@ldap_modify($this->conn, $dn, $object))
-        return TRUE;
-      else
-        raise_error(array("code" => ldap_errno($this->conn),
-                          "type" => "ldap",
-                          "message" => "Modify object failed: ".ldap_error($this->conn)),
-                    TRUE);
-      }
-    else
-      raise_error(array("type" => "ldap",
-                        "message" => "Modify object failed: no connection"),
-                  TRUE);
-    return FALSE;
-    }
-
-  function rename($dn, $newrdn, $parentdn)
-    {
-    if ($this->protocol < 3)
-      {
-      raise_error(array("type" => "ldap",
-                        "message" => "rename() support requires LDAPv3 or above "),
-                  TRUE);
-      return FALSE;
-      }
-
-    if ($this->conn)
-      {
-      if (@ldap_rename($this->conn, $dn, $newrdn, $parentdn, TRUE))
-        return TRUE;
-      else
-        raise_error(array("code" => ldap_errno($this->conn),
-                          "type" => "ldap",
-                          "message" => "Rename object failed: ".ldap_error($this->conn)),
-                    TRUE);
-      }
-    else
-      raise_error(array("type" => "ldap",
-                        "message" => "Rename object failed: no connection"),
-                  TRUE);
-    return FALSE;
-    }
-
-  function delete($dn)
-    {
-    if ($this->conn)
-      {
-      if (@ldap_delete($this->conn, $dn))
-        return TRUE;
-      else
-        raise_error(array("code" => ldap_errno($this->conn),
-                          "type" => "ldap",
-                          "message" => "Delete object failed: ".ldap_error($this->conn)),
-                    TRUE);
-      }
-    else
-      raise_error(array("type" => "ldap",
-                        "message" => "Delete object failed: no connection"),
-                  TRUE);
-    return FALSE;
-    }
-
+    // $this->filter = "(dn=*)";
+    $this->connect();
   }
 
-// vi: et ts=2 sw=2
-?>
+  /**
+   * PHP 4 object constructor
+   *
+   * @see  rcube_ldap::__construct
+   */
+  function rcube_ldap($p)
+  {
+    $this->__construct($p);
+  }
+  
+
+  /**
+   * Establish a connection to the LDAP server
+   */
+  function connect()
+  {
+    if (!function_exists('ldap_connect'))
+      raise_error(array('type' => 'ldap', 'message' => "No ldap support in this installation of PHP"), true);
+
+    if (is_resource($this->conn))
+      return true;
+    
+    if (!is_array($this->prop['hosts']))
+      $this->prop['hosts'] = array($this->prop['hosts']);
+
+    foreach ($this->prop['hosts'] as $host)
+    {
+      if ($lc = @ldap_connect($host, $this->prop['port']))
+      {
+        ldap_set_option($lc, LDAP_OPT_PROTOCOL_VERSION, $this->prop['port']);
+        $this->prop['host'] = $host;
+        $this->conn = $lc;
+        break;
+      }
+    }
+    
+    if (is_resource($this->conn))
+      $this->ready = true;
+    else
+      raise_error(array('type' => 'ldap', 'message' => "Could not connect to any LDAP server, tried $host:{$this->prop[port]} last"), true);
+  }
+
+
+  /**
+   * Merge with connect()?
+   */
+  function bind($dn=null, $pass=null)
+  {
+    if ($this->conn)
+    {
+      if ($dn)
+      {
+        if (@ldap_bind($this->conn, $dn, $pass))
+          return true;
+        else
+          raise_error(array('code' => ldap_errno($this->conn),
+                            'type' => 'ldap',
+                            'message' => "Bind failed for dn=$dn: ".ldap_error($this->conn)),
+                      true);
+      }
+      else
+      {
+        if (@ldap_bind($this->conn))
+          return true;
+        else
+          raise_error(array('code' => ldap_errno($this->conn),
+                            'type' => 'ldap',
+                            'message' => "Anonymous bind failed: ".ldap_error($this->conn)),
+                      true);
+        }
+    }
+    else
+      raise_error(array('type' => 'ldap', 'message' => "Attempted bind on nonexistent connection"), true);
+      
+    return false;
+    }
+
+
+  /**
+   * Close connection to LDAP server
+   */
+  function close()
+  {
+    if ($this->conn)
+      @ldap_unbind($this->conn);
+  }
+
+
+  /**
+   * Set internal list page
+   *
+   * @param  number  Page number to list
+   * @access public
+   */
+  function set_page($page)
+  {
+    $this->list_page = (int)$page;
+  }
+
+
+  /**
+   * Set internal page size
+   *
+   * @param  number  Number of messages to display on one page
+   * @access public
+   */
+  function set_pagesize($size)
+  {
+    $this->page_size = (int)$size;
+  }
+
+
+  /**
+   * Save a search string for future listings
+   *
+   * @param string ??
+   */
+  function set_search_set($filter)
+  {
+    $this->filter = $filter;
+  }
+  
+  
+  /**
+   * Getter for saved search properties
+   *
+   * @return mixed Search properties used by this class
+   */
+  function get_search_set()
+  {
+    return $this->filter;
+  }
+
+
+  /**
+   * Reset all saved results and search parameters
+   */
+  function reset()
+  {
+    $this->result = null;
+    $this->ldap_result = null;
+    $this->filter = '';
+  }
+  
+  
+  /**
+   * List the current set of contact records
+   *
+   * @param  array  List of cols to show
+   * @return array  Indexed list of contact records, each a hash array
+   */
+  function list_records($cols=null, $subset=0)
+  {
+    // exec LDAP search if no result resource is stored
+    if ($this->conn && !$this->ldap_result)
+      $this->_exec_search();
+    
+    // count contacts for this user
+    $this->result = $this->count();
+    
+    // we have a search result resource
+    if ($this->ldap_result && $this->result->count > 0)
+    {
+      if ($this->sort_col && $this->prop['scope'] !== "base")
+        @ldap_sort($this->conn, $this->ldap_result, $this->sort_col);
+        
+      $entries = ldap_get_entries($this->conn, $this->ldap_result);
+      for ($i = $this->result->first; $i < min($entries['count'], $this->result->first + $this->page_size); $i++)
+        $this->result->add($this->_ldap2result($entries[$i]));
+    }
+
+    return $this->result;
+  }
+
+
+  /**
+   * Search contacts
+   *
+   * @param array   List of fields to search in
+   * @param string  Search value
+   * @param boolean True if results are requested, False if count only
+   * @return Indexed list of contact records and 'count' value
+   */
+  function search($fields, $value, $select=true)
+  {
+    // special treatment for ID-based search
+    if ($fields == 'ID' || $fields == $this->primary_key)
+    {
+      $ids = explode(',', $value);
+      $result = new rcube_result_set();
+      foreach ($ids as $id)
+        if ($rec = $this->get_record($id, true))
+        {
+          $result->add($rec);
+          $result->count++;
+        }
+      
+      return $result;
+    }
+    
+    $filter = '(|';
+    $wc = $this->prop['fuzzy_search'] ? '*' : '';
+    if (is_array($this->prop['search_fields']))
+    {
+      foreach ($this->prop['search_fields'] as $k => $field)
+        $filter .= "($field=$wc" . rcube_ldap::quote_string($value) . "$wc)";
+    }
+    else
+    {
+      foreach ((array)$fields as $field)
+        if ($f = $this->_map_field($field))
+          $filter .= "($f=$wc" . rcube_ldap::quote_string($value) . "$wc)";
+    }
+    $filter .= ')';
+
+    // set filter string and execute search
+    $this->set_search_set($filter);
+    $this->_exec_search();
+    
+    if ($select)
+      $this->list_records();
+    else
+      $this->result = $this->count();
+   
+    return $this->result; 
+  }
+
+
+  /**
+   * Count number of available contacts in database
+   *
+   * @return Result array with values for 'count' and 'first'
+   */
+  function count()
+  {
+    $count = 0;
+    if ($this->conn && $this->ldap_result)
+      $count = ldap_count_entries($this->conn, $this->ldap_result);
+
+    return new rcube_result_set($count, ($this->list_page-1) * $this->page_size);
+  }
+
+
+  /**
+   * Return the last result set
+   *
+   * @return Result array or NULL if nothing selected yet
+   */
+  function get_result()
+  {
+    return $this->result;
+  }
+  
+  
+  /**
+   * Get a specific contact record
+   *
+   * @param mixed record identifier
+   * @return Hash array with all record fields or False if not found
+   */
+  function get_record($dn, $assoc=false)
+  {
+    $res = null;
+    if ($this->conn && $dn)
+    {
+      $this->ldap_result = @ldap_read($this->conn, base64_decode($dn), "(objectclass=*)", array_values($this->fieldmap));
+      $entry = @ldap_first_entry($this->conn, $this->ldap_result);
+      
+      if ($entry && ($rec = ldap_get_attributes($this->conn, $entry)))
+      {
+        $res = $this->_ldap2result($rec);
+        $this->result = new rcube_result_set(1);
+        $this->result->add($res);
+      }
+    }
+
+    return $assoc ? $res : $this->result;
+  }
+  
+  
+  /**
+   * Create a new contact record
+   *
+   * @param array Assoziative array with save data
+   * @return The create record ID on success, False on error
+   */
+  function insert($save_cols)
+  {
+    // TODO
+    return false;
+  }
+  
+  
+  /**
+   * Update a specific contact record
+   *
+   * @param mixed Record identifier
+   * @param array Assoziative array with save data
+   * @return True on success, False on error
+   */
+  function update($id, $save_cols)
+  {
+    // TODO    
+    return false;
+  }
+  
+  
+  /**
+   * Mark one or more contact records as deleted
+   *
+   * @param array  Record identifiers
+   */
+  function delete($ids)
+  {
+    // TODO
+    return false;
+  }
+
+
+  /**
+   * Execute the LDAP search based on the stored credentials
+   *
+   * @private
+   */
+  function _exec_search()
+  {
+    if ($this->conn && $this->filter)
+    {
+      $function = $this->prop['scope'] == 'sub' ? 'ldap_search' : ($this->prop['scope'] == 'base' ? 'ldap_read' : 'ldap_list');
+      $this->ldap_result = @$function($this->conn, $this->prop['base_dn'], $this->filter, array_values($this->fieldmap), 0, 0);
+      return true;
+    }
+    else
+      return false;
+  }
+  
+  
+  /**
+   * @private
+   */
+  function _ldap2result($rec)
+  {
+    $out = array();
+    
+    if ($rec['dn'])
+      $out[$this->primary_key] = base64_encode($rec['dn']);
+    
+    foreach ($this->fieldmap as $rf => $lf)
+    {
+      if ($rec[$lf]['count'])
+        $out[$rf] = $rec[$lf][0];
+    }
+    
+    return $out;
+  }
+  
+  
+  /**
+   * @private
+   */
+  function _map_field($field)
+  {
+    return $this->fieldmap[$field];
+  }
+  
+  
+  /**
+   * @static
+   */
+  function quote_string($str)
+  {
+    return strtr($str, array('*'=>'\2a', '('=>'\28', ')'=>'\29', '\\'=>'\5c'));
+  }
+
+
+}
+
+?>
\ No newline at end of file

--
Gitblit v1.9.1