3 +-----------------------------------------------------------------------+
4 | program/include/rcube_ldap.inc |
6 | This file is part of the RoundCube Webmail client |
7 | Copyright (C) 2006-2007, RoundCube Dev. - Switzerland |
8 | Licensed under the GNU GPL |
11 | Interface to an LDAP address directory |
13 +-----------------------------------------------------------------------+
14 | Author: Thomas Bruederli <roundcube@gmail.com> |
15 +-----------------------------------------------------------------------+
17 $Id: rcube_ldap.inc 1152 2008-02-28 20:01:57Z thomasb $
23 * Model class to access an LDAP address directory
25 * @package Addressbook
31 var $fieldmap = array();
35 var $ldap_result = null;
38 /** public properties */
39 var $primary_key = 'ID';
49 * @param array LDAP connection properties
50 * @param integer User-ID
52 function __construct($p)
56 foreach ($p as $prop => $value)
57 if (preg_match('/^(.+)_field$/', $prop, $matches))
58 $this->fieldmap[$matches[1]] = $value;
64 * PHP 4 object constructor
66 * @see rcube_ldap::__construct()
68 function rcube_ldap($p)
70 $this->__construct($p);
75 * Establish a connection to the LDAP server
79 if (!function_exists('ldap_connect'))
80 raise_error(array('type' => 'ldap', 'message' => "No ldap support in this installation of PHP"), true);
82 if (is_resource($this->conn))
85 if (!is_array($this->prop['hosts']))
86 $this->prop['hosts'] = array($this->prop['hosts']);
88 if (empty($this->prop['ldap_version']))
89 $this->prop['ldap_version'] = 3;
91 foreach ($this->prop['hosts'] as $host)
93 if ($lc = @ldap_connect($host, $this->prop['port']))
95 ldap_set_option($lc, LDAP_OPT_PROTOCOL_VERSION, $this->prop['ldap_version']);
96 $this->prop['host'] = $host;
102 if (is_resource($this->conn))
105 if (!empty($this->prop['bind_dn']) && !empty($this->prop['bind_pass']))
106 $this->ready = $this->bind($this->prop['bind_dn'], $this->prop['bind_pass']);
109 raise_error(array('type' => 'ldap', 'message' => "Could not connect to any LDAP server, tried $host:{$this->prop[port]} last"), true);
114 * Bind connection with DN and password
116 * @param string Bind DN
117 * @param string Bind password
118 * @return boolean True on success, False on error
120 function bind($dn, $pass)
125 if (@ldap_bind($this->conn, $dn, $pass))
130 'code' => ldap_errno($this->conn),
132 'message' => "Bind failed for dn=$dn: ".ldap_error($this->conn)),
141 * Close connection to LDAP server
147 @ldap_unbind($this->conn);
154 * Set internal list page
156 * @param number Page number to list
159 function set_page($page)
161 $this->list_page = (int)$page;
166 * Set internal page size
168 * @param number Number of messages to display on one page
171 function set_pagesize($size)
173 $this->page_size = (int)$size;
178 * Save a search string for future listings
180 * @param string Filter string
182 function set_search_set($filter)
184 $this->filter = $filter;
189 * Getter for saved search properties
191 * @return mixed Search properties used by this class
193 function get_search_set()
195 return $this->filter;
200 * Reset all saved results and search parameters
204 $this->result = null;
205 $this->ldap_result = null;
211 * List the current set of contact records
213 * @param array List of cols to show
214 * @param int Only return this number of records (not implemented)
215 * @return array Indexed list of contact records, each a hash array
217 function list_records($cols=null, $subset=0)
219 // add general filter to query
220 if (!empty($this->prop['filter']))
222 $filter = $this->prop['filter'];
223 $this->set_search_set($filter);
226 // exec LDAP search if no result resource is stored
227 if ($this->conn && !$this->ldap_result)
228 $this->_exec_search();
230 // count contacts for this user
231 $this->result = $this->count();
233 // we have a search result resource
234 if ($this->ldap_result && $this->result->count > 0)
236 if ($this->sort_col && $this->prop['scope'] !== "base")
237 @ldap_sort($this->conn, $this->ldap_result, $this->sort_col);
239 $entries = ldap_get_entries($this->conn, $this->ldap_result);
240 for ($i = $this->result->first; $i < min($entries['count'], $this->result->first + $this->page_size); $i++)
241 $this->result->add($this->_ldap2result($entries[$i]));
244 return $this->result;
251 * @param array List of fields to search in
252 * @param string Search value
253 * @param boolean True if results are requested, False if count only
254 * @return array Indexed list of contact records and 'count' value
256 function search($fields, $value, $strict=false, $select=true)
258 // special treatment for ID-based search
259 if ($fields == 'ID' || $fields == $this->primary_key)
261 $ids = explode(',', $value);
262 $result = new rcube_result_set();
263 foreach ($ids as $id)
264 if ($rec = $this->get_record($id, true))
274 $wc = !$strict && $this->prop['fuzzy_search'] ? '*' : '';
275 if (is_array($this->prop['search_fields']))
277 foreach ($this->prop['search_fields'] as $k => $field)
278 $filter .= "($field=$wc" . rcube_ldap::quote_string($value) . "$wc)";
282 foreach ((array)$fields as $field)
283 if ($f = $this->_map_field($field))
284 $filter .= "($f=$wc" . rcube_ldap::quote_string($value) . "$wc)";
288 // avoid double-wildcard if $value is empty
289 $filter = preg_replace('/\*+/', '*', $filter);
291 // add general filter to query
292 if (!empty($this->prop['filter']))
293 $filter = '(&(' . preg_replace('/^\(|\)$/', '', $this->prop['filter']) . ')' . $filter . ')';
295 // set filter string and execute search
296 $this->set_search_set($filter);
297 $this->_exec_search();
300 $this->list_records();
302 $this->result = $this->count();
304 return $this->result;
309 * Count number of available contacts in database
311 * @return object rcube_result_set Resultset with values for 'count' and 'first'
316 if ($this->conn && $this->ldap_result)
317 $count = ldap_count_entries($this->conn, $this->ldap_result);
319 return new rcube_result_set($count, ($this->list_page-1) * $this->page_size);
324 * Return the last result set
326 * @return object rcube_result_set Current resultset or NULL if nothing selected yet
328 function get_result()
330 return $this->result;
335 * Get a specific contact record
337 * @param mixed Record identifier
338 * @param boolean Return as associative array
339 * @return mixed Hash array or rcube_result_set with all record fields
341 function get_record($dn, $assoc=false)
344 if ($this->conn && $dn)
346 $this->ldap_result = @ldap_read($this->conn, base64_decode($dn), "(objectclass=*)", array_values($this->fieldmap));
347 $entry = @ldap_first_entry($this->conn, $this->ldap_result);
349 if ($entry && ($rec = ldap_get_attributes($this->conn, $entry)))
351 $res = $this->_ldap2result($rec);
352 $this->result = new rcube_result_set(1);
353 $this->result->add($res);
357 return $assoc ? $res : $this->result;
362 * Create a new contact record
364 * @param array Hash array with save data
365 * @return boolean The create record ID on success, False on error
367 function insert($save_cols)
375 * Update a specific contact record
377 * @param mixed Record identifier
378 * @param array Hash array with save data
379 * @return boolean True on success, False on error
381 function update($id, $save_cols)
389 * Mark one or more contact records as deleted
391 * @param array Record identifiers
392 * @return boolean True on success, False on error
394 function delete($ids)
402 * Execute the LDAP search based on the stored credentials
406 function _exec_search()
408 if ($this->conn && $this->filter)
410 $function = $this->prop['scope'] == 'sub' ? 'ldap_search' : ($this->prop['scope'] == 'base' ? 'ldap_read' : 'ldap_list');
411 $this->ldap_result = $function($this->conn, $this->prop['base_dn'], $this->filter, array_values($this->fieldmap), 0, 0);
422 function _ldap2result($rec)
427 $out[$this->primary_key] = base64_encode($rec['dn']);
429 foreach ($this->fieldmap as $rf => $lf)
431 if ($rec[$lf]['count'])
432 $out[$rf] = $rec[$lf][0];
442 function _map_field($field)
444 return $this->fieldmap[$field];
451 function quote_string($str)
453 return strtr($str, array('*'=>'\2a', '('=>'\28', ')'=>'\29', '\\'=>'\5c'));