thomascube
2007-08-10 31d9efd97d9da09605d4329457ee218cba48f82f
commit | author | age
d1d2c4 1 <?php
S 2
3 /*
4  +-----------------------------------------------------------------------+
5  | program/include/rcube_ldap.inc                                        |
6  |                                                                       |
7  | This file is part of the RoundCube Webmail client                     |
f11541 8  | Copyright (C) 2006-2007, RoundCube Dev. - Switzerland                 |
d1d2c4 9  | Licensed under the GNU GPL                                            |
S 10  |                                                                       |
11  | PURPOSE:                                                              |
f11541 12  |   Interface to an LDAP address directory                              |
d1d2c4 13  |                                                                       |
S 14  +-----------------------------------------------------------------------+
f11541 15  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
d1d2c4 16  +-----------------------------------------------------------------------+
S 17
18  $Id$
19
20 */
21
6d969b 22
T 23 /**
24  * Model class to access an LDAP address directory
25  *
26  * @package Addressbook
27  */
d1d2c4 28 class rcube_ldap
f11541 29 {
d1d2c4 30   var $conn;
f11541 31   var $prop = array();
T 32   var $fieldmap = array();
33   
34   var $filter = '';
35   var $result = null;
36   var $ldap_result = null;
37   var $sort_col = '';
38   
39   /** public properties */
40   var $primary_key = 'ID';
41   var $readonly = true;
42   var $list_page = 1;
43   var $page_size = 10;
44   var $ready = false;
45   
46   
47   /**
48    * Object constructor
49    *
50    * @param array LDAP connection properties
51    * @param integer User-ID
52    */
53   function __construct($p)
54   {
55     $this->prop = $p;
d1d2c4 56     
f11541 57     foreach ($p as $prop => $value)
T 58       if (preg_match('/^(.+)_field$/', $prop, $matches))
59         $this->fieldmap[$matches[1]] = $value;
d1d2c4 60     
f11541 61     // $this->filter = "(dn=*)";
T 62     $this->connect();
d1d2c4 63   }
S 64
f11541 65   /**
T 66    * PHP 4 object constructor
67    *
6d969b 68    * @see  rcube_ldap::__construct()
f11541 69    */
T 70   function rcube_ldap($p)
71   {
72     $this->__construct($p);
73   }
74   
75
76   /**
77    * Establish a connection to the LDAP server
78    */
79   function connect()
80   {
81     if (!function_exists('ldap_connect'))
82       raise_error(array('type' => 'ldap', 'message' => "No ldap support in this installation of PHP"), true);
83
84     if (is_resource($this->conn))
85       return true;
86     
87     if (!is_array($this->prop['hosts']))
88       $this->prop['hosts'] = array($this->prop['hosts']);
89
90     foreach ($this->prop['hosts'] as $host)
91     {
92       if ($lc = @ldap_connect($host, $this->prop['port']))
93       {
94         ldap_set_option($lc, LDAP_OPT_PROTOCOL_VERSION, $this->prop['port']);
95         $this->prop['host'] = $host;
96         $this->conn = $lc;
97         break;
98       }
99     }
100     
101     if (is_resource($this->conn))
e3caaf 102     {
f11541 103       $this->ready = true;
e3caaf 104       if (!empty($this->prop['bind_dn']) && !empty($this->prop['bind_pass']))
T 105         $this->ready = $this->bind($this->prop['bind_dn'], $this->prop['bind_pass']);
106     }
f11541 107     else
T 108       raise_error(array('type' => 'ldap', 'message' => "Could not connect to any LDAP server, tried $host:{$this->prop[port]} last"), true);
109   }
110
111
112   /**
e3caaf 113    * Bind connection with DN and password
6d969b 114    *
T 115    * @param string Bind DN
116    * @param string Bind password
117    * @return boolean True on success, False on error
f11541 118    */
e3caaf 119   function bind($dn, $pass)
f11541 120   {
e3caaf 121     if (!$this->conn)
T 122       return false;
123     
124     if (@ldap_bind($this->conn, $dn, $pass))
125       return true;
f11541 126     else
e3caaf 127     {
T 128       raise_error(array(
129         'code' => ldap_errno($this->conn),
130         'type' => 'ldap',
131         'message' => "Bind failed for dn=$dn: ".ldap_error($this->conn)),
132       true);
f11541 133     }
e3caaf 134     
T 135     return false;
136   }
f11541 137
T 138
139   /**
140    * Close connection to LDAP server
141    */
142   function close()
143   {
144     if ($this->conn)
145       @ldap_unbind($this->conn);
146   }
147
148
149   /**
150    * Set internal list page
151    *
152    * @param  number  Page number to list
153    * @access public
154    */
155   function set_page($page)
156   {
157     $this->list_page = (int)$page;
158   }
159
160
161   /**
162    * Set internal page size
163    *
164    * @param  number  Number of messages to display on one page
165    * @access public
166    */
167   function set_pagesize($size)
168   {
169     $this->page_size = (int)$size;
170   }
171
172
173   /**
174    * Save a search string for future listings
175    *
6d969b 176    * @param string Filter string
f11541 177    */
T 178   function set_search_set($filter)
179   {
180     $this->filter = $filter;
181   }
182   
183   
184   /**
185    * Getter for saved search properties
186    *
187    * @return mixed Search properties used by this class
188    */
189   function get_search_set()
190   {
191     return $this->filter;
192   }
193
194
195   /**
196    * Reset all saved results and search parameters
197    */
198   function reset()
199   {
200     $this->result = null;
201     $this->ldap_result = null;
202     $this->filter = '';
203   }
204   
205   
206   /**
207    * List the current set of contact records
208    *
209    * @param  array  List of cols to show
6d969b 210    * @param  int    Only return this number of records (not implemented)
f11541 211    * @return array  Indexed list of contact records, each a hash array
T 212    */
213   function list_records($cols=null, $subset=0)
214   {
215     // exec LDAP search if no result resource is stored
216     if ($this->conn && !$this->ldap_result)
217       $this->_exec_search();
218     
219     // count contacts for this user
220     $this->result = $this->count();
221     
222     // we have a search result resource
223     if ($this->ldap_result && $this->result->count > 0)
224     {
225       if ($this->sort_col && $this->prop['scope'] !== "base")
226         @ldap_sort($this->conn, $this->ldap_result, $this->sort_col);
227         
228       $entries = ldap_get_entries($this->conn, $this->ldap_result);
229       for ($i = $this->result->first; $i < min($entries['count'], $this->result->first + $this->page_size); $i++)
230         $this->result->add($this->_ldap2result($entries[$i]));
231     }
232
233     return $this->result;
234   }
235
236
237   /**
238    * Search contacts
239    *
240    * @param array   List of fields to search in
241    * @param string  Search value
242    * @param boolean True if results are requested, False if count only
6d969b 243    * @return array  Indexed list of contact records and 'count' value
f11541 244    */
3fc00e 245   function search($fields, $value, $strict=false, $select=true)
f11541 246   {
T 247     // special treatment for ID-based search
248     if ($fields == 'ID' || $fields == $this->primary_key)
249     {
250       $ids = explode(',', $value);
251       $result = new rcube_result_set();
252       foreach ($ids as $id)
253         if ($rec = $this->get_record($id, true))
254         {
255           $result->add($rec);
256           $result->count++;
257         }
258       
259       return $result;
260     }
261     
262     $filter = '(|';
3fc00e 263     $wc = !$strict && $this->prop['fuzzy_search'] ? '*' : '';
f11541 264     if (is_array($this->prop['search_fields']))
T 265     {
266       foreach ($this->prop['search_fields'] as $k => $field)
267         $filter .= "($field=$wc" . rcube_ldap::quote_string($value) . "$wc)";
268     }
269     else
270     {
271       foreach ((array)$fields as $field)
272         if ($f = $this->_map_field($field))
273           $filter .= "($f=$wc" . rcube_ldap::quote_string($value) . "$wc)";
274     }
275     $filter .= ')';
e3caaf 276     
T 277     // add general filter to query
278     if (!empty($this->prop['filter']))
279       $filter = '(&'.$this->prop['filter'] . $filter . ')';
f11541 280
T 281     // set filter string and execute search
282     $this->set_search_set($filter);
283     $this->_exec_search();
284     
285     if ($select)
286       $this->list_records();
287     else
288       $this->result = $this->count();
289    
290     return $this->result; 
291   }
292
293
294   /**
295    * Count number of available contacts in database
296    *
6d969b 297    * @return object rcube_result_set Resultset with values for 'count' and 'first'
f11541 298    */
T 299   function count()
300   {
301     $count = 0;
302     if ($this->conn && $this->ldap_result)
303       $count = ldap_count_entries($this->conn, $this->ldap_result);
304
305     return new rcube_result_set($count, ($this->list_page-1) * $this->page_size);
306   }
307
308
309   /**
310    * Return the last result set
311    *
6d969b 312    * @return object rcube_result_set Current resultset or NULL if nothing selected yet
f11541 313    */
T 314   function get_result()
315   {
316     return $this->result;
317   }
318   
319   
320   /**
321    * Get a specific contact record
322    *
6d969b 323    * @param mixed   Record identifier
T 324    * @param boolean Return as associative array
325    * @return mixed  Hash array or rcube_result_set with all record fields
f11541 326    */
T 327   function get_record($dn, $assoc=false)
328   {
329     $res = null;
330     if ($this->conn && $dn)
331     {
332       $this->ldap_result = @ldap_read($this->conn, base64_decode($dn), "(objectclass=*)", array_values($this->fieldmap));
333       $entry = @ldap_first_entry($this->conn, $this->ldap_result);
334       
335       if ($entry && ($rec = ldap_get_attributes($this->conn, $entry)))
336       {
337         $res = $this->_ldap2result($rec);
338         $this->result = new rcube_result_set(1);
339         $this->result->add($res);
340       }
341     }
342
343     return $assoc ? $res : $this->result;
344   }
345   
346   
347   /**
348    * Create a new contact record
349    *
6d969b 350    * @param array    Hash array with save data
T 351    * @return boolean The create record ID on success, False on error
f11541 352    */
T 353   function insert($save_cols)
354   {
355     // TODO
356     return false;
357   }
358   
359   
360   /**
361    * Update a specific contact record
362    *
363    * @param mixed Record identifier
6d969b 364    * @param array Hash array with save data
T 365    * @return boolean True on success, False on error
f11541 366    */
T 367   function update($id, $save_cols)
368   {
369     // TODO    
370     return false;
371   }
372   
373   
374   /**
375    * Mark one or more contact records as deleted
376    *
377    * @param array  Record identifiers
6d969b 378    * @return boolean True on success, False on error
f11541 379    */
T 380   function delete($ids)
381   {
382     // TODO
383     return false;
384   }
385
386
387   /**
388    * Execute the LDAP search based on the stored credentials
389    *
6d969b 390    * @access private
f11541 391    */
T 392   function _exec_search()
393   {
394     if ($this->conn && $this->filter)
395     {
396       $function = $this->prop['scope'] == 'sub' ? 'ldap_search' : ($this->prop['scope'] == 'base' ? 'ldap_read' : 'ldap_list');
e3caaf 397       $this->ldap_result = $function($this->conn, $this->prop['base_dn'], $this->filter, array_values($this->fieldmap), 0, 0);
f11541 398       return true;
T 399     }
400     else
401       return false;
402   }
403   
404   
405   /**
6d969b 406    * @access private
f11541 407    */
T 408   function _ldap2result($rec)
409   {
410     $out = array();
411     
412     if ($rec['dn'])
413       $out[$this->primary_key] = base64_encode($rec['dn']);
414     
415     foreach ($this->fieldmap as $rf => $lf)
416     {
417       if ($rec[$lf]['count'])
418         $out[$rf] = $rec[$lf][0];
419     }
420     
421     return $out;
422   }
423   
424   
425   /**
6d969b 426    * @access private
f11541 427    */
T 428   function _map_field($field)
429   {
430     return $this->fieldmap[$field];
431   }
432   
433   
434   /**
435    * @static
436    */
437   function quote_string($str)
438   {
439     return strtr($str, array('*'=>'\2a', '('=>'\28', ')'=>'\29', '\\'=>'\5c'));
440   }
441
442
443 }
444
445 ?>