]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/pear/DB/ldap.php
rcs_id no longer makes sense with Subversion global version number
[SourceForge/phpwiki.git] / lib / pear / DB / ldap.php
1 <?php
2 //
3 // Pear DB LDAP - Database independent query interface definition
4 // for PHP's LDAP extension.
5 //
6 // Copyright (C) 2002 Ludovico Magnocavallo <ludo@sumatrasolutions.com>
7 //
8 //  This library is free software; you can redistribute it and/or
9 //  modify it under the terms of the GNU Lesser General Public
10 //  License as published by the Free Software Foundation; either
11 //  version 2.1 of the License, or (at your option) any later version.
12 //
13 //  This library is distributed in the hope that it will be useful,
14 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
15 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 //  Lesser General Public License for more details.
17 //
18 //  You should have received a copy of the GNU Lesser General Public
19 //  License along with this library; if not, write to the Free Software
20 //  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
21 //
22 // Contributors
23 // - Piotr Roszatycki <Piotr_Roszatycki@netia.net.pl>
24 //   DB_ldap::base() method, support for LDAP sequences, various fixes
25 //
26 // $Id$
27 //
28 // Based on DB 1.3 from the pear.php.net repository. 
29 // The only modifications made have been modification of the include paths. 
30 //
31 // rcs_id('$Id$');
32 // rcs_id('From Pear CVS: Id: ldap.php,v 1.9 2002/02/11 12:59:37 mj Exp');
33
34 require_once 'DB/common.php';
35 define("DB_ERROR_BIND_FAILED",     -26);
36 define("DB_ERROR_UNKNOWN_LDAP_ACTION",     -27);
37
38 /**
39  * LDAP result class
40  *
41  * LDAP_result extends DB_result to provide specific LDAP
42  * result methods.
43  *
44  * @version 1.0
45  * @author Ludovico Magnocavallo <ludo@sumatrasolutions.com>
46  * @package DB
47  */
48
49 class LDAP_result extends DB_result
50 {
51
52     // {{{ properties
53     
54     /**
55      * data returned from ldap_entries()
56      * @access private
57      */
58     var $_entries   = null;
59     /**
60      * result rows as hash of records
61      * @access private
62      */
63     var $_recordset = null;
64     /**
65      * current record as hash
66      * @access private
67      */
68     var $_record    = null;
69     
70     // }}}
71     // {{{ constructor
72
73     /**
74      * class constructor, calls DB_result constructor
75      * @param ref $dbh reference to the db instance
76      * @param resource $result ldap command result
77      */
78     function LDAP_result(&$dbh, $result)
79     {
80         $this->DB_result($dbh, $result);
81     }
82
83     /**
84      * fetch rows of data into $this->_recordset
85      *
86      * called once as soon as something needs to be returned
87      * @access private
88      * @param resource $result ldap command result
89      * @return boolean true
90      */
91     function getRows() {
92         if ($this->_recordset === null) {
93             // begin processing result into recordset
94             $this->_entries = ldap_get_entries($this->dbh->connection, $this->result);
95             $this->row_counter = $this->_entries['count'];
96             $i = 1;
97             $rs_template = array();
98             if (count($this->dbh->attributes) > 0) {
99                 reset($this->dbh->attributes);
100                 while (list($a_index, $a_name) = each($this->dbh->attributes)) $rs_template[$a_name] = '';
101             }
102             while (list($entry_idx, $entry) = each($this->_entries)) {
103                 // begin first loop, iterate through entries
104                 if (!empty($this->dbh->limit_from) && ($i < $this->dbh->limit_from)) continue;
105                 if (!empty($this->dbh->limit_count) && ($i > $this->dbh->limit_count)) break;
106                 $rs = $rs_template;
107                 if (!is_array($entry)) continue;
108                 while (list($attr, $attr_values) = each($entry)) {
109                     // begin second loop, iterate through attributes
110                     if (is_int($attr) || $attr == 'count') continue;
111                     if (is_string($attr_values)) $rs[$attr] = $attr_values;
112                     else {
113                         $value = '';
114                         while (list($value_idx, $attr_value) = each($attr_values)) {
115                             // begin third loop, iterate through attribute values
116                             if (!is_int($value_idx)) continue;
117                             if (empty($value)) $value = $attr_value;
118                             else {
119                                 if (is_array($value)) $value[] = $attr_value;
120                                 else $value = array($value, $attr_value);
121                             }
122 //                          else $value .= "\n$attr_value";
123                             // end third loop
124                         }
125                         $rs[$attr] = $value;
126                     }
127                     // end second loop
128                 }
129                 reset($rs);
130                 $this->_recordset[$entry_idx] = $rs;
131                 $i++;
132                 // end first loop
133             }
134             $this->_entries = null;
135             if (!is_array($this->_recordset))
136                 $this->_recordset = array();
137             if (!empty($this->dbh->sorting)) {
138                 $sorting_method = (!empty($this->dbh->sorting_method) ? $this->dbh->sorting_method : 'cmp');
139                 uksort($this->_recordset, array(&$this, $sorting_method));
140             }
141             reset($this->_recordset);
142             // end processing result into recordset
143         }
144         return DB_OK;
145     }
146     
147     
148     /**
149      * Fetch and return a row of data (it uses driver->fetchInto for that)
150      * @param int $fetchmode  format of fetched row
151      * @param int $rownum     the row number to fetch
152      *
153      * @return  array a row of data, NULL on no more rows or PEAR_Error on error
154      *
155      * @access public
156      */
157     function &fetchRow($fetchmode = DB_FETCHMODE_DEFAULT, $rownum=null)
158     {
159         $this->getRows();
160         if (count($this->_recordset) == 0) return null;
161         if ($this->_record !== null) $this->_record = next($this->_recordset);
162         else $this->_record = current($this->_recordset);
163         $row = $this->_record;
164         return $row;
165     }
166
167
168     /**
169      * Fetch a row of data into an existing variable.
170      *
171      * @param  mixed     $arr        reference to data containing the row
172      * @param  integer   $fetchmode  format of fetched row
173      * @param  integer   $rownum     the row number to fetch
174      *
175      * @return  mixed  DB_OK on success, NULL on no more rows or
176      *                 a DB_Error object on error
177      *
178      * @access public     
179      */
180
181     function fetchInto(&$ar, $fetchmode = DB_FETCHMODE_DEFAULT, $rownum = null)
182     {
183         $this->getRows();
184         if ($this->_record !== null) $this->_record = next($this->_recordset);
185         else $this->_record = current($this->_recordset);
186         $ar = $this->_record;
187         if (!$ar) {
188             return null;
189         }
190         return DB_OK;
191     }
192     
193     /**
194      * return all records
195      *
196      * returns a hash of all records, basically returning
197      * a copy of $this->_recordset
198      * @param  integer   $fetchmode  format of fetched row
199      * @param  integer   $rownum     the row number to fetch (not used, here for interface compatibility)
200      *
201      * @return  mixed  DB_OK on success, NULL on no more rows or
202      *                 a DB_Error object on error
203      *
204      * @access public     
205      */
206     function fetchAll($fetchmode = DB_FETCHMODE_DEFAULT, $rownum = null)
207     {
208         $this->getRows();
209         return($this->_recordset);
210     }
211
212     /**
213      * Get the the number of columns in a result set.
214      *
215      * @return int the number of columns, or a DB error
216      *
217      * @access public
218      */
219     function numCols($result)
220     {
221         $this->getRows();
222         return(count(array_keys($this->_record)));
223     }
224
225     function cmp($a, $b)
226     {
227         return(strcmp(strtolower($this->_recordset[$a][$this->dbh->sorting]), strtolower($this->_recordset[$b][$this->dbh->sorting])));
228     }
229   
230     /**
231      * Get the number of rows in a result set.
232      *
233      * @return int the number of rows, or a DB error
234      *
235      * @access public     
236      */
237     function numRows()
238     {
239         $this->getRows();
240         return $this->row_counter;
241     }
242
243     /**
244      * Get the next result if a batch of queries was executed.
245      *
246      * @return bool true if a new result is available or false if not.
247      *
248      * @access public     
249      */
250     function nextResult()
251     {
252         return $this->dbh->nextResult($this->result);
253     }
254
255     /**
256      * Frees the resources allocated for this result set.
257      * @return  int     error code
258      *
259      * @access public     
260      */
261     function free()
262     {
263         $this->_recordset = null;
264         $this->_record = null;
265         ldap_free_result($this->result);
266         $this->result = null;
267         return true;
268     }
269
270     /**
271     * @deprecated
272     */
273     function tableInfo($mode = null)
274     {
275         return $this->dbh->tableInfo($this->result, $mode);
276     }
277
278     /**
279     * returns the actual rows number
280     * @return integer
281     */    
282     function getRowCounter()
283     {
284         $this->getRows();
285         return $this->row_counter;
286     }
287 }
288
289 /**
290  * LDAP DB interface class
291  *
292  * LDAP extends DB_common to provide DB compliant
293  * access to LDAP servers
294  *
295  * @version 1.0
296  * @author Ludovico Magnocavallo <ludo@sumatrasolutions.com>
297  * @package DB
298  */
299
300 class DB_ldap extends DB_common
301 {
302     // {{{ properties
303     
304     /**
305      * LDAP connection
306      * @access private
307      */
308     var $connection;
309     /**
310      * base dn
311      * @access private
312      */
313     var $base           = '';
314     /**
315      * query base dn
316      * @access private
317      */
318     var $q_base           = '';
319     /**
320      * array of LDAP actions that only manipulate data
321      * returning a true/false value
322      * @access private
323      */
324     var $manip          = array('add', 'compare', 'delete', 'modify', 'mod_add', 'mod_del', 'mod_replace', 'rename');
325     /**
326      * store the real LDAP action to perform
327      * (ie PHP ldap function to call) for a query
328      * @access private
329      */
330     var $q_action       = '';
331     /**
332      * store optional parameters passed
333      *  to the real LDAP action
334      * @access private
335      */
336     var $q_params       = array();
337
338     // }}}
339
340     /**
341      * Constructor, calls DB_common constructor
342      *
343      * @see DB_common::DB_common()
344      */
345     function DB_ldap()
346     {
347         $this->DB_common();
348         $this->phptype = 'ldap';
349         $this->dbsyntax = 'ldap';
350         $this->features = array(
351             'prepare'       => false,
352             'pconnect'      => false,
353             'transactions'  => false,
354             'limit'         => false
355         );
356     }
357
358     /**
359      * Connect and bind to LDAP server with either anonymous or authenticated bind depending on dsn info
360      *
361      * @param array $dsninfo dsn info as passed by DB::connect()
362      * @param boolean $persistent kept for interface compatibility
363      * @return DB_OK if successfully connected. A DB error code is returned on failure.
364      */
365     function connect($dsninfo, $persistent = false)
366     {
367         if (!DB::assertExtension('ldap'))
368             return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
369
370         $this->dsn = $dsninfo;
371         $user   = $dsninfo['username'];
372         $pw     = $dsninfo['password'];
373         $host   = $dsninfo['hostspec'];
374         $this->base = $dsninfo['database'];
375
376         if ($host) {
377             $conn = ldap_connect($host);
378         } else {
379             return $this->raiseError("unknown host $host");
380         }
381         if (!$conn) {
382             return $this->raiseError(DB_ERROR_CONNECT_FAILED);
383         }
384         if ($user && $pw) {
385             $bind = ldap_bind($conn, "${user}," . $this->base, $pw);
386         } else {
387             $bind = ldap_bind($conn);
388         }
389         if (!$bind) {
390             return $this->raiseError(DB_ERROR_BIND_FAILED);
391         }
392         $this->connection = $conn;
393         return DB_OK;
394     }
395
396     /**
397      * Unbinds from LDAP server
398      *
399      * @return int ldap_unbind() return value
400      */
401     function disconnect()
402     {
403         $ret = @ldap_unbind($this->connection);
404         $this->connection = null;
405         return $ret;
406     }
407
408
409     /**
410      * Performs a request against the LDAP server
411      *
412      * The type of request (and the corresponding PHP ldap function called)
413      * depend on two additional parameters, added in respect to the
414      * DB_common interface.
415      *
416      * @param string $filter text of the request to send to the LDAP server
417      * @param string $action type of request to perform, defaults to search (ldap_search())
418      * @param array $params array of additional parameters to pass to the PHP ldap function requested
419      * @return result from ldap function or DB Error object if no result
420      */
421     function simpleQuery($filter, $action = null, $params = null)
422     {
423         if ($action === null) {
424             $action = (!empty($this->q_action) ? $this->q_action : 'search');
425         }
426         if ($params === null) {
427             $params = (count($this->q_params) > 0 ? $this->q_params : array());
428         }
429         if (!$this->isManip($action)) {
430             $base = $this->q_base ? $this->q_base : $this->base;
431             $attributes = array();
432             $attrsonly = 0;
433             $sizelimit = 0;
434             $timelimit = 0;
435             $deref = LDAP_DEREF_NEVER;
436             $sorting = '';
437             $sorting_method = '';
438             reset($params);
439             while (list($k, $v) = each($params)) {
440                 if (isset(${$k})) ${$k} = $v;
441             }
442             $this->sorting = $sorting;
443             $this->sorting_method = $sorting_method;
444             $this->attributes = $attributes;
445             if ($action == 'search')
446                 $result = @ldap_search($this->connection, $base, $filter, $attributes, $attrsonly, $sizelimit, $timelimit, $deref);
447             else if ($action == 'list')
448                 $result = @ldap_list($this->connection, $base, $filter, $attributes, $attrsonly, $sizelimit, $timelimit, $deref);
449             else if ($action == 'read')
450                 $result = @ldap_read($this->connection, $base, $filter, $attributes, $attrsonly, $sizelimit, $timelimit, $deref);
451             else
452                 return $this->raiseError(DB_ERROR_UNKNOWN_LDAP_ACTION);
453             if (!$result) {
454                 return $this->raiseError();
455             }
456         } else {
457             # If first argument is an array, it contains the entry with DN.
458             if (is_array($filter)) {
459                 $entry = $filter;
460                 $filter = $entry["dn"];
461             } else {
462                 $entry = array();
463             }
464             unset($entry["dn"]);
465             $attribute      = '';
466             $value          = '';
467             $newrdn         = '';
468             $newparent      = '';
469             $deleteoldrdn   = false;
470             reset($params);
471             while (list($k, $v) = each($params)) {
472                 if (isset(${$k})) ${$k} = $v;
473             }
474             if ($action == 'add')
475                 $result = @ldap_add($this->connection, $filter, $entry);
476             else if ($action == 'compare')
477                 $result = @ldap_add($this->connection, $filter, $attribute, $value);
478             else if ($action == 'delete')
479                 $result = @ldap_delete($this->connection, $filter);
480             else if ($action == 'modify')
481                 $result = @ldap_modify($this->connection, $filter, $entry);
482             else if ($action == 'mod_add')
483                 $result = @ldap_mod_add($this->connection, $filter, $entry);
484             else if ($action == 'mod_del')
485                 $result = @ldap_mod_del($this->connection, $filter, $entry);
486             else if ($action == 'mod_replace')
487                 $result = @ldap_mod_replace($this->connection, $filter, $entry);
488             else if ($action == 'rename')
489                 $result = @ldap_rename($this->connection, $filter, $newrdn, $newparent, $deleteoldrdn);
490             else
491                 return $this->raiseError(DB_ERROR_UNKNOWN_LDAP_ACTION);
492             if (!$result) {
493                 return $this->raiseError();
494             }
495         }
496         $this->freeQuery();
497         return $result;
498     }
499
500     /**
501      * Executes a query performing variables substitution in the query text
502      *
503      * @param string $stmt text of the request to send to the LDAP server
504      * @param array $data query variables values to substitute
505      * @param string $action type of request to perform, defaults to search (ldap_search())
506      * @param array $params array of additional parameters to pass to the PHP ldap function requested
507      * @return LDAP_result object or DB Error object if no result
508      * @see DB_common::executeEmulateQuery $this->simpleQuery()
509      */
510     function execute($stmt, $data = false, $action = 'search', $params = array())
511     {
512         $this->q_action = $action;
513         $this->q_params = $params;
514         $realquery = $this->executeEmulateQuery($stmt, $data);
515         if (DB::isError($realquery)) {
516             return $realquery;
517         }
518         $result = $this->simpleQuery($realquery);
519         if (DB::isError($result) || $result === DB_OK) {
520             return $result;
521         } else {
522             return new LDAP_result($this, $result);
523         }
524     }
525
526     /**
527      * Executes multiple queries performing variables substitution for each query
528      *
529      * @param string $stmt text of the request to send to the LDAP server
530      * @param array $data query variables values to substitute
531      * @param string $action type of request to perform, defaults to search (ldap_search())
532      * @param array $params array of additional parameters to pass to the PHP ldap function requested
533      * @return LDAP_result object or DB Error object if no result
534      * @see DB_common::executeMultiple
535      */
536     function executeMultiple($stmt, &$data, $action = 'search', $params = array())
537     {
538         $this->q_action = $action;
539         $this->q_params = $params;
540         return(parent::executeMultiple($stmt, $data));
541     }
542
543     /**
544      * Executes a query substituting variables if any are present
545      *
546      * @param string $query text of the request to send to the LDAP server
547      * @param array $data query variables values to substitute
548      * @param string $action type of request to perform, defaults to search (ldap_search())
549      * @param array $params array of additional parameters to pass to the PHP ldap function requested
550      * @return LDAP_result object or DB Error object if no result
551      * @see DB_common::prepare() $this->execute()$this->simpleQuery()
552      */
553     function &query($query, $data = array(), $action = 'search', $params = array()) {
554         $this->q_action = $action;
555         $this->q_params = $params;
556         if (sizeof($data) > 0) {
557             $sth = $this->prepare($query);
558             if (DB::isError($sth)) {
559                 return $sth;
560             }
561             return $this->execute($sth, $data);
562         } else {
563             $result = $this->simpleQuery($query);
564             if (DB::isError($result) || $result === DB_OK) {
565                 return $result;
566             } else {
567                 return new LDAP_result($this, $result);
568             }
569         }
570     }
571
572     /**
573      * Modifies a query to return only a set of rows, stores $from and $count for LDAP_result
574      *
575      * @param string $query text of the request to send to the LDAP server
576      * @param int $from record position from which to start returning data
577      * @param int $count number of records to return
578      * @return modified query text (no modifications are made, see above)
579      */
580     function modifyLimitQuery($query, $from, $count)
581     {
582         $this->limit_from = $from;
583         $this->limit_count = $count;
584         return $query;
585     }
586     
587     /**
588      * Executes a query returning only a specified number of rows
589      *
590      * This method only saves the $from and $count parameters for LDAP_result
591      * where the actual records processing takes place
592      *
593      * @param string $query text of the request to send to the LDAP server
594      * @param int $from record position from which to start returning data
595      * @param int $count number of records to return
596      * @param string $action type of request to perform, defaults to search (ldap_search())
597      * @param array $params array of additional parameters to pass to the PHP ldap function requested
598      * @return LDAP_result object or DB Error object if no result
599      */
600     function limitQuery($query, $from, $count, $action = 'search', $params = array())
601     {
602         $query = $this->modifyLimitQuery($query, $from, $count);
603         $this->q_action = $action;
604         $this->q_params = $params;
605         return $this->query($query, $action, $params);
606     }
607
608     /**
609      * Fetch the first column of the first row of data returned from
610      * a query.  Takes care of doing the query and freeing the results
611      * when finished.
612      *
613      * @param $query the SQL query
614      * @param $data if supplied, prepare/execute will be used
615      *        with this array as execute parameters
616      * @param string $action type of request to perform, defaults to search (ldap_search())
617      * @param array $params array of additional parameters to pass to the PHP ldap function requested
618      * @return array
619      * @see DB_common::getOne()
620      * @access public
621      */
622     function &getOne($query, $data = array(), $action = 'search', $params = array())
623     {
624         $this->q_action = $action;
625         $this->q_params = $params;
626         return(parent::getOne($query, $data));
627     }
628
629     /**
630      * Fetch the first row of data returned from a query.  Takes care
631      * of doing the query and freeing the results when finished.
632      *
633      * @param $query the SQL query
634      * @param $fetchmode the fetch mode to use
635      * @param $data array if supplied, prepare/execute will be used
636      *        with this array as execute parameters
637      * @param string $action type of request to perform, defaults to search (ldap_search())
638      * @param array $params array of additional parameters to pass to the PHP ldap function requested
639      * @access public
640      * @return array the first row of results as an array indexed from
641      * 0, or a DB error code.
642      * @see DB_common::getRow()
643      * @access public
644      */
645     function &getRow($query,
646                      $data = null,
647                      $fetchmode = DB_FETCHMODE_DEFAULT,
648                      $action = 'search', $params = array())
649     {
650         $this->q_action = $action;
651         $this->q_params = $params;
652         return(parent::getRow($query, $data, $fetchmode));
653     }
654
655     /**
656      * Fetch the first column of data returned from a query.  Takes care
657      * of doing the query and freeing the results when finished.
658      *
659      * @param $query the SQL query
660      * @param $col which column to return (integer [column number,
661      * starting at 0] or string [column name])
662      * @param $data array if supplied, prepare/execute will be used
663      *        with this array as execute parameters
664      * @param string $action type of request to perform, defaults to search (ldap_search())
665      * @param array $params array of additional parameters to pass to the PHP ldap function requested
666      * @access public
667      * @return array an indexed array with the data from the first
668      * row at index 0, or a DB error code.
669      * @see DB_common::getCol()
670      * @access public
671      */
672     function &getCol($query, $col = 0, $data = array(), $action = 'search', $params = array())
673     {
674         $this->q_action = $action;
675         $this->q_params = $params;
676         return(parent::getCol($query, $col, $data));
677     }
678
679     /**
680      * Calls DB_common::getAssoc()
681      *
682      * @param $query the SQL query
683      * @param $force_array (optional) used only when the query returns
684      * exactly two columns.  If true, the values of the returned array
685      * will be one-element arrays instead of scalars.
686      * starting at 0] or string [column name])
687      * @param array $data if supplied, prepare/execute will be used
688      *        with this array as execute parameters
689      * @param $fetchmode the fetch mode to use
690      * @param boolean $group see DB_Common::getAssoc()
691      * @param string $action type of request to perform, defaults to search (ldap_search())
692      * @param array $params array of additional parameters to pass to the PHP ldap function requested
693      * @access public
694      * @return array an indexed array with the data from the first
695      * row at index 0, or a DB error code.
696      * @see DB_common::getAssoc()
697      * @access public
698      */
699     function &getAssoc($query, $force_array = false, $data = array(),
700                        $fetchmode = DB_FETCHMODE_ORDERED, $group = false,
701                        $action = 'search', $params = array())
702     {
703         $this->q_action = $action;
704         $this->q_params = $params;
705         return(parent::getAssoc($query, $force_array, $data, $fetchmode, $group));
706     }
707     
708     /**
709      * Fetch all the rows returned from a query.
710      *
711      * @param $query the SQL query
712      * @param array $data if supplied, prepare/execute will be used
713      *        with this array as execute parameters
714      * @param $fetchmode the fetch mode to use
715      * @param string $action type of request to perform, defaults to search (ldap_search())
716      * @param array $params array of additional parameters to pass to the PHP ldap function requested
717      * @access public
718      * @return array an nested array, or a DB error
719      * @see DB_common::getAll()
720      */
721     function &getAll($query,
722                      $data = null,
723                      $fetchmode = DB_FETCHMODE_DEFAULT,
724                      $action = 'search', $params = array())
725     {
726         $this->q_action = $action;
727         $this->q_params = $params;
728         return(parent::getAll($query, $data, $fetchmode));
729     }
730     
731     function numRows($result)
732     {
733         return $result->numRows();
734     }
735
736     function getTables()
737     {
738         return $this->raiseError(DB_ERROR_NOT_CAPABLE);
739     }
740
741     function getListOf($type)
742     {
743         return $this->raiseError(DB_ERROR_NOT_CAPABLE);
744     }
745
746     function isManip($action)
747     {
748         return(in_array($action, $this->manip));
749     }
750
751     function freeResult()
752     {
753         return true;
754     }
755
756     function freeQuery($query = '')
757     {
758         $this->q_action = '';
759         $this->q_base   = '';
760         $this->q_params = array();
761         $this->attributes = null;
762         $this->sorting = '';
763         return true;
764     }
765
766     function base($base = null)
767     {
768         $this->q_base = ($base !== null) ? $base : null;
769         return true;
770     }
771
772     /**
773      * Get the next value in a sequence.
774      *
775      * LDAP provides transactions for only one entry and we need to
776      * prevent race condition. If unique value before and after modify
777      * aren't equal then wait and try again.
778      *
779      * The name of sequence is LDAP DN of entry.
780      *
781      * @access public
782      * @param string $seq_name the DN of the sequence
783      * @param bool $ondemand whether to create the sequence on demand
784      * @return a sequence integer, or a DB error
785      */
786     function nextId($seq_name, $ondemand = true)
787     {
788         $repeat = 0;
789         do {
790             // Get the sequence entry
791             $this->base($seq_name);
792             $this->pushErrorHandling(PEAR_ERROR_RETURN);
793             $data = $this->getRow("objectClass=*");
794             $this->popErrorHandling();
795
796             if (DB::isError($data)) {
797                 // DB_ldap doesn't use DB_ERROR_NOT_FOUND
798                 if ($ondemand && $repeat == 0
799                 && $data->getCode() == DB_ERROR) {
800                 // Try to create sequence and repeat
801                     $repeat = 1;
802                     $data = $this->createSequence($seq_name);
803                     if (DB::isError($data)) {
804                         return $this->raiseError($data);
805                     }
806                 } else {
807                     // Other error
808                     return $this->raiseError($data);
809                 }
810             } else {
811                 // Increment sequence value
812                 $data["cn"]++;
813                 // Unique identificator of transaction
814                 $seq_unique = mt_rand();
815                 $data["uid"] = $seq_unique;
816                 // Modify the LDAP entry
817                 $this->pushErrorHandling(PEAR_ERROR_RETURN);
818                 $data = $this->simpleQuery($data, 'modify');
819                 $this->popErrorHandling();
820                 if (DB::isError($data)) {
821                     return $this->raiseError($data);
822                 }
823                 // Get the entry and check if it contains our unique value
824                 $this->base($seq_name);
825                 $data = $this->getRow("objectClass=*");
826                 if (DB::isError($data)) {
827                     return $this->raiseError($data);
828                 }
829                 if ($data["uid"] != $seq_unique) {
830                     // It is not our entry. Wait a little time and repeat
831                     sleep(1);
832                     $repeat = 1;
833                 } else {
834                     $repeat = 0;
835                 }
836             }
837         } while ($repeat);
838         
839         if (DB::isError($data)) {
840             return $data;
841         }
842         return $data["cn"];
843     }
844
845     /**
846      * Create the sequence
847      *
848      * The sequence entry is based on core schema with extensibleObject,
849      * so it should work with any LDAP server which doesn't check schema
850      * or supports extensibleObject object class.
851      *
852      * Sequence name have to be DN started with "sn=$seq_id,", i.e.:
853      *
854      * $seq_name = "sn=uidNumber,ou=sequences,dc=php,dc=net";
855      *
856      * dn: $seq_name
857      * objectClass: top
858      * objectClass: extensibleObject
859      * sn: $seq_id
860      * cn: $seq_value
861      * uid: $seq_uniq
862      *
863      * @param string $seq_name the DN of the sequence
864      * @return mixed DB_OK on success or DB error on error
865      * @access public
866      */
867     function createSequence($seq_name)
868     {
869         // Extract $seq_id from DN
870         ereg("^([^,]*),", $seq_name, $regs);
871         $seq_id = $regs[1];
872
873         // Create the sequence entry
874         $data = array(
875             dn => $seq_name,
876             objectclass => array("top", "extensibleObject"),
877             sn => $seq_id,
878             cn => 0,
879             uid => 0
880         );
881
882         // Add the LDAP entry
883         $this->pushErrorHandling(PEAR_ERROR_RETURN);
884         $data = $this->simpleQuery($data, 'add');
885         $this->popErrorHandling();
886         return $data;
887     }
888
889     /**
890      * Drop a sequence
891      *
892      * @param string $seq_name the DN of the sequence
893      * @return mixed DB_OK on success or DB error on error
894      * @access public
895      */
896     function dropSequence($seq_name)
897     {
898         // Delete the sequence entry
899         $data = array(
900             dn => $seq_name,
901         );
902         $this->pushErrorHandling(PEAR_ERROR_RETURN);
903         $data = $this->simpleQuery($data, 'delete');
904         $this->popErrorHandling();
905         return $data;
906     }
907
908 }
909
910 /*
911  * Local variables:
912  * tab-width: 4
913  * c-basic-offset: 4
914  * End:
915  */
916 ?>