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