source: github/program/include/rcube_ldap.inc @ 6b603da

HEADcourier-fixdev-browser-capabilitiespdorelease-0.6release-0.7release-0.8
Last change on this file since 6b603da was 6b603da, checked in by thomascube <thomas@…>, 6 years ago

LDAP improvements

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