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