2 /* vim: set expandtab tabstop=4 shiftwidth=4: */
4 * File containing the Net_LDAP2 interface class.
10 * @author Tarjej Huse <tarjei@bergfald.no>
11 * @author Jan Wagner <wagner@netsols.de>
12 * @author Del <del@babel.com.au>
13 * @author Benedikt Hallinger <beni@php.net>
14 * @copyright 2003-2007 Tarjej Huse, Jan Wagner, Del Elson, Benedikt Hallinger
15 * @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
16 * @version SVN: $Id: LDAP2.php 286788 2009-08-04 06:05:49Z beni $
17 * @link http://pear.php.net/package/Net_LDAP2/
23 require_once 'PEAR.php';
24 require_once 'Net/LDAP2/RootDSE.php';
25 require_once 'Net/LDAP2/Schema.php';
26 require_once 'Net/LDAP2/Entry.php';
27 require_once 'Net/LDAP2/Search.php';
28 require_once 'Net/LDAP2/Util.php';
29 require_once 'Net/LDAP2/Filter.php';
30 require_once 'Net/LDAP2/LDIF.php';
31 require_once 'Net/LDAP2/SchemaCache.interface.php';
32 require_once 'Net/LDAP2/SimpleFileSchemaCache.php';
35 * Error constants for errors that are not LDAP errors.
37 define('NET_LDAP2_ERROR', 1000);
42 define('NET_LDAP2_VERSION', '2.0.7');
45 * Net_LDAP2 - manipulate LDAP servers the right way!
49 * @author Tarjej Huse <tarjei@bergfald.no>
50 * @author Jan Wagner <wagner@netsols.de>
51 * @author Del <del@babel.com.au>
52 * @author Benedikt Hallinger <beni@php.net>
53 * @copyright 2003-2007 Tarjej Huse, Jan Wagner, Del Elson, Benedikt Hallinger
54 * @license http://www.gnu.org/copyleft/lesser.html LGPL
55 * @link http://pear.php.net/package/Net_LDAP2/
57 class Net_LDAP2 extends PEAR
60 * Class configuration array
62 * host = the ldap host to connect to
63 * (may be an array of several hosts to try)
64 * port = the server port
65 * version = ldap version (defaults to v 3)
66 * starttls = when set, ldap_start_tls() is run after connecting.
67 * bindpw = no explanation needed
68 * binddn = the DN to bind as.
70 * options = hash of ldap options to set (opt => val)
71 * filter = default search filter
72 * scope = default search scope
74 * Newly added in 2.0.0RC4, for auto-reconnect:
75 * auto_reconnect = if set to true then the class will automatically
76 * attempt to reconnect to the LDAP server in certain
77 * failure conditionswhen attempting a search, or other
78 * LDAP operation. Defaults to false. Note that if you
79 * set this to true, calls to search() may block
80 * indefinitely if there is a catastrophic server failure.
81 * min_backoff = minimum reconnection delay period (in seconds).
82 * current_backoff = initial reconnection delay period (in seconds).
83 * max_backoff = maximum reconnection delay period (in seconds).
88 protected $_config = array('host' => 'localhost',
96 'filter' => '(objectClass=*)',
98 'auto_reconnect' => false,
100 'current_backoff' => 1,
101 'max_backoff' => 32);
104 * List of hosts we try to establish a connection to
109 protected $_host_list = array();
112 * List of hosts that are known to be down.
117 protected $_down_host_list = array();
120 * LDAP resource link.
125 protected $_link = false;
128 * Net_LDAP2_Schema object
130 * This gets set and returned by {@link schema()}
133 * @var object Net_LDAP2_Schema
135 protected $_schema = null;
138 * Schema cacher function callback
140 * @see registerSchemaCache()
143 protected $_schema_cache = null;
146 * Cache for attribute encoding checks
149 * @var array Hash with attribute names as key and boolean value
150 * to determine whether they should be utf8 encoded or not.
152 protected $_schemaAttrs = array();
155 * Cache for rootDSE objects
157 * Hash with requested rootDSE attr names as key and rootDSE object as value
159 * Since the RootDSE object itself may request a rootDSE object,
160 * {@link rootDse()} caches successful requests.
161 * Internally, Net_LDAP2 needs several lookups to this object, so
162 * caching increases performance significally.
167 protected $_rootDSE_cache = array();
170 * Returns the Net_LDAP2 Release version, may be called statically
173 * @return string Net_LDAP2 version
175 public static function getVersion()
177 return NET_LDAP2_VERSION;
181 * Configure Net_LDAP2, connect and bind
183 * Use this method as starting point of using Net_LDAP2
184 * to establish a connection to your LDAP server.
186 * Static function that returns either an error object or the new Net_LDAP2
187 * object. Something like a factory. Takes a config array with the needed
190 * @param array $config Configuration array
193 * @return Net_LDAP2_Error|Net_LDAP2 Net_LDAP2_Error or Net_LDAP2 object
195 public static function &connect($config = array())
197 $ldap_check = self::checkLDAPExtension();
198 if (self::iserror($ldap_check)) {
202 @$obj = new Net_LDAP2($config);
204 // todo? better errorhandling for setConfig()?
206 // connect and bind with credentials in config
208 if (self::isError($err)) {
216 * Net_LDAP2 constructor
218 * Sets the config array
220 * Please note that the usual way of getting Net_LDAP2 to work is
221 * to call something like:
222 * <code>$ldap = Net_LDAP2::connect($ldap_config);</code>
224 * @param array $config Configuration array
230 public function __construct($config = array())
232 $this->PEAR('Net_LDAP2_Error');
233 $this->setConfig($config);
237 * Sets the internal configuration array
239 * @param array $config Configuration array
244 protected function setConfig($config)
247 // Parameter check -- probably should raise an error here if config
250 if (! is_array($config)) {
254 foreach ($config as $k => $v) {
255 if (isset($this->_config[$k])) {
256 $this->_config[$k] = $v;
258 // map old (Net_LDAP2) parms to new ones
261 $this->_config["binddn"] = $v;
264 $this->_config["bindpw"] = $v;
267 $this->_config["starttls"] = $v;
270 $this->_config["basedn"] = $v;
277 // Ensure the host list is an array.
279 if (is_array($this->_config['host'])) {
280 $this->_host_list = $this->_config['host'];
282 if (strlen($this->_config['host']) > 0) {
283 $this->_host_list = array($this->_config['host']);
285 $this->_host_list = array();
286 // ^ this will cause an error in performConnect(),
287 // so the user is notified about the failure
292 // Reset the down host list, which seems like a sensible thing to do
293 // if the config is being reset for some reason.
295 $this->_down_host_list = array();
299 * Bind or rebind to the ldap-server
301 * This function binds with the given dn and password to the server. In case
302 * no connection has been made yet, it will be started and startTLS issued
305 * The internal bind configuration is not being updated, so if you call
306 * bind() without parameters, you can rebind with the credentials
307 * provided at first connecting to the server.
309 * @param string $dn Distinguished name for binding
310 * @param string $password Password for binding
313 * @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
315 public function bind($dn = null, $password = null)
317 // fetch current bind credentials
319 $dn = $this->_config["binddn"];
321 if (is_null($password)) {
322 $password = $this->_config["bindpw"];
325 // Connect first, if we haven't so far.
326 // This will also bind us to the server.
327 if ($this->_link === false) {
328 // store old credentials so we can revert them later
329 // then overwrite config with new bind credentials
330 $olddn = $this->_config["binddn"];
331 $oldpw = $this->_config["bindpw"];
333 // overwrite bind credentials in config
334 // so performConnect() knows about them
335 $this->_config["binddn"] = $dn;
336 $this->_config["bindpw"] = $password;
338 // try to connect with provided credentials
339 $msg = $this->performConnect();
341 // reset to previous config
342 $this->_config["binddn"] = $olddn;
343 $this->_config["bindpw"] = $oldpw;
345 // see if bind worked
346 if (self::isError($msg)) {
350 // do the requested bind as we are
351 // asked to bind manually
354 $msg = @ldap_bind($this->_link);
357 $msg = @ldap_bind($this->_link, $dn, $password);
359 if (false === $msg) {
360 return PEAR::raiseError("Bind failed: " .
361 @ldap_error($this->_link),
362 @ldap_errno($this->_link));
369 * Connect to the ldap-server
371 * This function connects to the LDAP server specified in
372 * the configuration, binds and set up the LDAP protocol as needed.
375 * @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
377 protected function performConnect()
379 // Note: Connecting is briefly described in RFC1777.
380 // Basicly it works like this:
381 // 1. set up TCP connection
382 // 2. secure that connection if neccessary
383 // 3a. setLDAPVersion to tell server which version we want to speak
385 // 3c. setLDAPVersion to tell server which version we want to speak
386 // together with a test for supported versions
387 // 4. set additional protocol options
389 // Return true if we are already connected.
390 if ($this->_link !== false) {
394 // Connnect to the LDAP server if we are not connected. Note that
395 // with some LDAP clients, ldapperformConnect returns a link value even
396 // if no connection is made. We need to do at least one anonymous
397 // bind to ensure that a connection is actually valid.
399 // Ref: http://www.php.net/manual/en/function.ldap-connect.php
401 // Default error message in case all connection attempts
402 // fail but no message is set
403 $current_error = new PEAR_Error('Unknown connection error');
405 // Catch empty $_host_list arrays.
406 if (!is_array($this->_host_list) || count($this->_host_list) == 0) {
407 $current_error = PEAR::raiseError('No Servers configured! Please '.
408 'pass in an array of servers to Net_LDAP2');
409 return $current_error;
412 // Cycle through the host list.
413 foreach ($this->_host_list as $host) {
415 // Ensure we have a valid string for host name
416 if (is_array($host)) {
417 $current_error = PEAR::raiseError('No Servers configured! '.
418 'Please pass in an one dimensional array of servers to '.
419 'Net_LDAP2! (multidimensional array detected!)');
423 // Skip this host if it is known to be down.
424 if (in_array($host, $this->_down_host_list)) {
428 // Record the host that we are actually connecting to in case
430 $this->_config['host'] = $host;
432 // Attempt a connection.
433 $this->_link = @ldap_connect($host, $this->_config['port']);
434 if (false === $this->_link) {
435 $current_error = PEAR::raiseError('Could not connect to ' .
436 $host . ':' . $this->_config['port']);
437 $this->_down_host_list[] = $host;
441 // If we're supposed to use TLS, do so before we try to bind,
442 // as some strict servers only allow binding via secure connections
443 if ($this->_config["starttls"] === true) {
444 if (self::isError($msg = $this->startTLS())) {
445 $current_error = $msg;
446 $this->_link = false;
447 $this->_down_host_list[] = $host;
452 // Try to set the configured LDAP version on the connection if LDAP
453 // server needs that before binding (eg OpenLDAP).
454 // This could be necessary since rfc-1777 states that the protocol version
455 // has to be set at the bind request.
456 // We use force here which means that the test in the rootDSE is skipped;
457 // this is neccessary, because some strict LDAP servers only allow to
458 // read the LDAP rootDSE (which tells us the supported protocol versions)
459 // with authenticated clients.
460 // This may fail in which case we try again after binding.
461 // In this case, most probably the bind() or setLDAPVersion()-call
462 // below will also fail, providing error messages.
463 $version_set = false;
464 $ignored_err = $this->setLDAPVersion(0, true);
465 if (!self::isError($ignored_err)) {
469 // Attempt to bind to the server. If we have credentials configured,
470 // we try to use them, otherwise its an anonymous bind.
471 // As stated by RFC-1777, the bind request should be the first
472 // operation to be performed after the connection is established.
473 // This may give an protocol error if the server does not support
474 // V2 binds and the above call to setLDAPVersion() failed.
475 // In case the above call failed, we try an V2 bind here and set the
476 // version afterwards (with checking to the rootDSE).
477 $msg = $this->bind();
478 if (self::isError($msg)) {
479 // The bind failed, discard link and save error msg.
480 // Then record the host as down and try next one
481 if ($msg->getCode() == 0x02 && !$version_set) {
482 // provide a finer grained error message
483 // if protocol error arieses because of invalid version
484 $msg = new Net_LDAP2_Error($msg->getMessage().
485 " (could not set LDAP protocol version to ".
486 $this->_config['version'].")",
489 $this->_link = false;
490 $current_error = $msg;
491 $this->_down_host_list[] = $host;
495 // Set desired LDAP version if not successfully set before.
496 // Here, a check against the rootDSE is performed, so we get a
497 // error message if the server does not support the version.
498 // The rootDSE entry should tell us which LDAP versions are
499 // supported. However, some strict LDAP servers only allow
500 // bound suers to read the rootDSE.
502 if (self::isError($msg = $this->setLDAPVersion())) {
503 $current_error = $msg;
504 $this->_link = false;
505 $this->_down_host_list[] = $host;
510 // Set LDAP parameters, now we know we have a valid connection.
511 if (isset($this->_config['options']) &&
512 is_array($this->_config['options']) &&
513 count($this->_config['options'])) {
514 foreach ($this->_config['options'] as $opt => $val) {
515 $err = $this->setOption($opt, $val);
516 if (self::isError($err)) {
517 $current_error = $err;
518 $this->_link = false;
519 $this->_down_host_list[] = $host;
525 // At this stage we have connected, bound, and set up options,
526 // so we have a known good LDAP server. Time to go home.
531 // All connection attempts have failed, return the last error.
532 return $current_error;
536 * Reconnect to the ldap-server.
538 * In case the connection to the LDAP
539 * service has dropped out for some reason, this function will reconnect,
540 * and re-bind if a bind has been attempted in the past. It is probably
541 * most useful when the server list provided to the new() or connect()
542 * function is an array rather than a single host name, because in that
543 * case it will be able to connect to a failover or secondary server in
544 * case the primary server goes down.
546 * This doesn't return anything, it just tries to re-establish
547 * the current connection. It will sleep for the current backoff
548 * period (seconds) before attempting the connect, and if the
549 * connection fails it will double the backoff period, but not
550 * try again. If you want to ensure a reconnection during a
551 * transient period of server downtime then you need to call this
552 * function in a loop.
555 * @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
557 protected function performReconnect()
560 // Return true if we are already connected.
561 if ($this->_link !== false) {
565 // Default error message in case all connection attempts
566 // fail but no message is set
567 $current_error = new PEAR_Error('Unknown connection error');
569 // Sleep for a backoff period in seconds.
570 sleep($this->_config['current_backoff']);
572 // Retry all available connections.
573 $this->_down_host_list = array();
574 $msg = $this->performConnect();
576 // Bail out if that fails.
577 if (self::isError($msg)) {
578 $this->_config['current_backoff'] =
579 $this->_config['current_backoff'] * 2;
580 if ($this->_config['current_backoff'] > $this->_config['max_backoff']) {
581 $this->_config['current_backoff'] = $this->_config['max_backoff'];
586 // Now we should be able to safely (re-)bind.
587 $msg = $this->bind();
588 if (self::isError($msg)) {
589 $this->_config['current_backoff'] = $this->_config['current_backoff'] * 2;
590 if ($this->_config['current_backoff'] > $this->_config['max_backoff']) {
591 $this->_config['current_backoff'] = $this->_config['max_backoff'];
594 // _config['host'] should have had the last connected host stored in it
595 // by performConnect(). Since we are unable to bind to that host we can safely
596 // assume that it is down or has some other problem.
597 $this->_down_host_list[] = $this->_config['host'];
601 // At this stage we have connected, bound, and set up options,
602 // so we have a known good LDAP server. Time to go home.
603 $this->_config['current_backoff'] = $this->_config['min_backoff'];
608 * Starts an encrypted session
611 * @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
613 public function startTLS()
615 // Test to see if the server supports TLS first.
616 // This is done via testing the extensions offered by the server.
617 // The OID 1.3.6.1.4.1.1466.20037 tells us, if TLS is supported.
618 $rootDSE = $this->rootDse();
619 if (self::isError($rootDSE)) {
620 return $this->raiseError("Unable to fetch rootDSE entry ".
621 "to see if TLS is supoported: ".$rootDSE->getMessage(), $rootDSE->getCode());
624 $supported_extensions = $rootDSE->getValue('supportedExtension');
625 if (self::isError($supported_extensions)) {
626 return $this->raiseError("Unable to fetch rootDSE attribute 'supportedExtension' ".
627 "to see if TLS is supoported: ".$supported_extensions->getMessage(), $supported_extensions->getCode());
630 if (in_array('1.3.6.1.4.1.1466.20037', $supported_extensions)) {
631 if (false === @ldap_start_tls($this->_link)) {
632 return $this->raiseError("TLS not started: " .
633 @ldap_error($this->_link),
634 @ldap_errno($this->_link));
638 return $this->raiseError("Server reports that it does not support TLS");
643 * alias function of startTLS() for perl-ldap interface
648 public function start_tls()
650 $args = func_get_args();
651 return call_user_func_array(array( &$this, 'startTLS' ), $args);
655 * Close LDAP connection.
657 * Closes the connection. Use this when the session is over.
661 public function done()
667 * Alias for {@link done()}
672 public function disconnect()
682 public function _Net_LDAP2()
684 @ldap_close($this->_link);
688 * Add a new entryobject to a directory.
690 * Use add to add a new Net_LDAP2_Entry object to the directory.
691 * This also links the entry to the connection used for the add,
692 * if it was a fresh entry ({@link Net_LDAP2_Entry::createFresh()})
694 * @param Net_LDAP2_Entry &$entry Net_LDAP2_Entry
696 * @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
698 public function add(&$entry)
700 if (!$entry instanceof Net_LDAP2_Entry) {
701 return PEAR::raiseError('Parameter to Net_LDAP2::add() must be a Net_LDAP2_Entry object.');
704 // Continue attempting the add operation in a loop until we
705 // get a success, a definitive failure, or the world ends.
708 $link = $this->getLink();
710 if ($link === false) {
711 // We do not have a successful connection yet. The call to
712 // getLink() would have kept trying if we wanted one. Go
714 return PEAR::raiseError("Could not add entry " . $entry->dn() .
715 " no valid LDAP connection could be found.");
718 if (@ldap_add($link, $entry->dn(), $entry->getValues())) {
719 // entry successfully added, we should update its $ldap reference
720 // in case it is not set so far (fresh entry)
721 if (!$entry->getLDAP() instanceof Net_LDAP2) {
722 $entry->setLDAP($this);
724 // store, that the entry is present inside the directory
725 $entry->markAsNew(false);
728 // We have a failure. What type? We may be able to reconnect
730 $error_code = @ldap_errno($link);
731 $error_name = $this->errorMessage($error_code);
733 if (($error_name === 'LDAP_OPERATIONS_ERROR') &&
734 ($this->_config['auto_reconnect'])) {
736 // The server has become disconnected before trying the
737 // operation. We should try again, possibly with a different
739 $this->_link = false;
740 $this->performReconnect();
742 // Errors other than the above catched are just passed
743 // back to the user so he may react upon them.
744 return PEAR::raiseError("Could not add entry " . $entry->dn() . " " .
753 * Delete an entry from the directory
755 * The object may either be a string representing the dn or a Net_LDAP2_Entry
756 * object. When the boolean paramter recursive is set, all subentries of the
757 * entry will be deleted as well.
759 * @param string|Net_LDAP2_Entry $dn DN-string or Net_LDAP2_Entry
760 * @param boolean $recursive Should we delete all children recursive as well?
763 * @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
765 public function delete($dn, $recursive = false)
767 if ($dn instanceof Net_LDAP2_Entry) {
770 if (false === is_string($dn)) {
771 return PEAR::raiseError("Parameter is not a string nor an entry object!");
773 // Recursive delete searches for children and calls delete for them
775 $result = @ldap_list($this->_link, $dn, '(objectClass=*)', array(null), 0, 0);
776 if (@ldap_count_entries($this->_link, $result)) {
777 $subentry = @ldap_first_entry($this->_link, $result);
778 $this->delete(@ldap_get_dn($this->_link, $subentry), true);
779 while ($subentry = @ldap_next_entry($this->_link, $subentry)) {
780 $this->delete(@ldap_get_dn($this->_link, $subentry), true);
785 // Continue attempting the delete operation in a loop until we
786 // get a success, a definitive failure, or the world ends.
788 $link = $this->getLink();
790 if ($link === false) {
791 // We do not have a successful connection yet. The call to
792 // getLink() would have kept trying if we wanted one. Go
794 return PEAR::raiseError("Could not add entry " . $dn .
795 " no valid LDAP connection could be found.");
798 if (@ldap_delete($link, $dn)) {
799 // entry successfully deleted.
802 // We have a failure. What type?
803 // We may be able to reconnect and try again.
804 $error_code = @ldap_errno($link);
805 $error_name = $this->errorMessage($error_code);
807 if (($this->errorMessage($error_code) === 'LDAP_OPERATIONS_ERROR') &&
808 ($this->_config['auto_reconnect'])) {
809 // The server has become disconnected before trying the
810 // operation. We should try again, possibly with a
812 $this->_link = false;
813 $this->performReconnect();
815 } elseif ($error_code == 66) {
816 // Subentries present, server refused to delete.
817 // Deleting subentries is the clients responsibility, but
818 // since the user may not know of the subentries, we do not
819 // force that here but instead notify the developer so he
820 // may take actions himself.
821 return PEAR::raiseError("Could not delete entry $dn because of subentries. Use the recursive parameter to delete them.");
824 // Errors other than the above catched are just passed
825 // back to the user so he may react upon them.
826 return PEAR::raiseError("Could not delete entry " . $dn . " " .
835 * Modify an ldapentry directly on the server
837 * This one takes the DN or a Net_LDAP2_Entry object and an array of actions.
838 * This array should be something like this:
840 * array('add' => array('attribute1' => array('val1', 'val2'),
841 * 'attribute2' => array('val1')),
842 * 'delete' => array('attribute1'),
843 * 'replace' => array('attribute1' => array('val1')),
844 * 'changes' => array('add' => ...,
846 * 'delete' => array('attribute1', 'attribute2' => array('val1')))
848 * The changes array is there so the order of operations can be influenced
849 * (the operations are done in order of appearance).
850 * The order of execution is as following:
851 * 1. adds from 'add' array
852 * 2. deletes from 'delete' array
853 * 3. replaces from 'replace' array
854 * 4. changes (add, replace, delete) in order of appearance
855 * All subarrays (add, replace, delete, changes) may be given at the same time.
857 * The function calls the corresponding functions of an Net_LDAP2_Entry
858 * object. A detailed description of array structures can be found there.
860 * Unlike the modification methods provided by the Net_LDAP2_Entry object,
861 * this method will instantly carry out an update() after each operation,
862 * thus modifying "directly" on the server.
864 * @param string|Net_LDAP2_Entry $entry DN-string or Net_LDAP2_Entry
865 * @param array $parms Array of changes
868 * @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
870 public function modify($entry, $parms = array())
872 if (is_string($entry)) {
873 $entry = $this->getEntry($entry);
874 if (self::isError($entry)) {
878 if (!$entry instanceof Net_LDAP2_Entry) {
879 return PEAR::raiseError("Parameter is not a string nor an entry object!");
882 // Perform changes mentioned separately
883 foreach (array('add', 'delete', 'replace') as $action) {
884 if (isset($parms[$action])) {
885 $msg = $entry->$action($parms[$action]);
886 if (self::isError($msg)) {
889 $entry->setLDAP($this);
891 // Because the @ldap functions are called inside Net_LDAP2_Entry::update(),
892 // we have to trap the error codes issued from that if we want to support
895 $msg = $entry->update();
897 if (self::isError($msg)) {
898 // We have a failure. What type? We may be able to reconnect
900 $error_code = $msg->getCode();
901 $error_name = $this->errorMessage($error_code);
903 if (($this->errorMessage($error_code) === 'LDAP_OPERATIONS_ERROR') &&
904 ($this->_config['auto_reconnect'])) {
906 // The server has become disconnected before trying the
907 // operation. We should try again, possibly with a different
909 $this->_link = false;
910 $this->performReconnect();
914 // Errors other than the above catched are just passed
915 // back to the user so he may react upon them.
916 return PEAR::raiseError("Could not modify entry: ".$msg->getMessage());
919 // modification succeedet, evaluate next change
926 // perform combined changes in 'changes' array
927 if (isset($parms['changes']) && is_array($parms['changes'])) {
928 foreach ($parms['changes'] as $action => $value) {
930 // Because the @ldap functions are called inside Net_LDAP2_Entry::update,
931 // we have to trap the error codes issued from that if we want to support
934 $msg = $this->modify($entry, array($action => $value));
936 if (self::isError($msg)) {
937 // We have a failure. What type? We may be able to reconnect
939 $error_code = $msg->getCode();
940 $error_name = $this->errorMessage($error_code);
942 if (($this->errorMessage($error_code) === 'LDAP_OPERATIONS_ERROR') &&
943 ($this->_config['auto_reconnect'])) {
945 // The server has become disconnected before trying the
946 // operation. We should try again, possibly with a different
948 $this->_link = false;
949 $this->performReconnect();
952 // Errors other than the above catched are just passed
953 // back to the user so he may react upon them.
957 // modification succeedet, evaluate next change
968 * Run a ldap search query
970 * Search is used to query the ldap-database.
971 * $base and $filter may be ommitted. The one from config will
972 * then be used. $base is either a DN-string or an Net_LDAP2_Entry
973 * object in which case its DN willb e used.
975 * Params may contain:
977 * scope: The scope which will be used for searching
978 * base - Just one entry
979 * sub - The whole tree
980 * one - Immediately below $base
981 * sizelimit: Limit the number of entries returned (default: 0 = unlimited),
982 * timelimit: Limit the time spent for searching (default: 0 = unlimited),
983 * attrsonly: If true, the search will only return the attribute names,
984 * attributes: Array of attribute names, which the entry should contain.
985 * It is good practice to limit this to just the ones you need.
987 * deref: By default aliases are dereferenced to locate the base object for the search, but not when
988 * searching subordinates of the base object. This may be changed by specifying one of the
991 * never - Do not dereference aliases in searching or in locating the base object of the search.
992 * search - Dereference aliases in subordinates of the base object in searching, but not in
993 * locating the base object of the search.
997 * Please note, that you cannot override server side limitations to sizelimit
998 * and timelimit: You can always only lower a given limit.
1000 * @param string|Net_LDAP2_Entry $base LDAP searchbase
1001 * @param string|Net_LDAP2_Filter $filter LDAP search filter or a Net_LDAP2_Filter object
1002 * @param array $params Array of options
1005 * @return Net_LDAP2_Search|Net_LDAP2_Error Net_LDAP2_Search object or Net_LDAP2_Error object
1006 * @todo implement search controls (sorting etc)
1008 public function search($base = null, $filter = null, $params = array())
1010 if (is_null($base)) {
1011 $base = $this->_config['basedn'];
1013 if ($base instanceof Net_LDAP2_Entry) {
1014 $base = $base->dn(); // fetch DN of entry, making searchbase relative to the entry
1016 if (is_null($filter)) {
1017 $filter = $this->_config['filter'];
1019 if ($filter instanceof Net_LDAP2_Filter) {
1020 $filter = $filter->asString(); // convert Net_LDAP2_Filter to string representation
1022 if (PEAR::isError($filter)) {
1025 if (PEAR::isError($base)) {
1029 /* setting searchparameters */
1030 (isset($params['sizelimit'])) ? $sizelimit = $params['sizelimit'] : $sizelimit = 0;
1031 (isset($params['timelimit'])) ? $timelimit = $params['timelimit'] : $timelimit = 0;
1032 (isset($params['attrsonly'])) ? $attrsonly = $params['attrsonly'] : $attrsonly = 0;
1033 (isset($params['attributes'])) ? $attributes = $params['attributes'] : $attributes = array();
1035 // Ensure $attributes to be an array in case only one
1036 // attribute name was given as string
1037 if (!is_array($attributes)) {
1038 $attributes = array($attributes);
1041 // reorganize the $attributes array index keys
1042 // sometimes there are problems with not consecutive indexes
1043 $attributes = array_values($attributes);
1045 // scoping makes searches faster!
1046 $scope = (isset($params['scope']) ? $params['scope'] : $this->_config['scope']);
1050 $search_function = 'ldap_list';
1053 $search_function = 'ldap_read';
1056 $search_function = 'ldap_search';
1059 // Continue attempting the search operation until we get a success
1060 // or a definitive failure.
1062 $link = $this->getLink();
1063 $search = @call_user_func($search_function,
1072 if ($err = @ldap_errno($link)) {
1074 // Errorcode 32 = no such object, i.e. a nullresult.
1075 return $obj = new Net_LDAP2_Search ($search, $this, $attributes);
1076 } elseif ($err == 4) {
1077 // Errorcode 4 = sizelimit exeeded.
1078 return $obj = new Net_LDAP2_Search ($search, $this, $attributes);
1079 } elseif ($err == 87) {
1080 // bad search filter
1081 return $this->raiseError($this->errorMessage($err) . "($filter)", $err);
1082 } elseif (($err == 1) && ($this->_config['auto_reconnect'])) {
1083 // Errorcode 1 = LDAP_OPERATIONS_ERROR but we can try a reconnect.
1084 $this->_link = false;
1085 $this->performReconnect();
1087 $msg = "\nParameters:\nBase: $base\nFilter: $filter\nScope: $scope";
1088 return $this->raiseError($this->errorMessage($err) . $msg, $err);
1091 return $obj = new Net_LDAP2_Search($search, $this, $attributes);
1097 * Set an LDAP option
1099 * @param string $option Option to set
1100 * @param mixed $value Value to set Option to
1103 * @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
1105 public function setOption($option, $value)
1108 if (defined($option)) {
1109 if (@ldap_set_option($this->_link, constant($option), $value)) {
1112 $err = @ldap_errno($this->_link);
1114 $msg = @ldap_err2str($err);
1116 $err = NET_LDAP2_ERROR;
1117 $msg = $this->errorMessage($err);
1119 return $this->raiseError($msg, $err);
1122 return $this->raiseError("Unkown Option requested");
1125 return $this->raiseError("Could not set LDAP option: No LDAP connection");
1130 * Get an LDAP option value
1132 * @param string $option Option to get
1135 * @return Net_LDAP2_Error|string Net_LDAP2_Error or option value
1137 public function getOption($option)
1140 if (defined($option)) {
1141 if (@ldap_get_option($this->_link, constant($option), $value)) {
1144 $err = @ldap_errno($this->_link);
1146 $msg = @ldap_err2str($err);
1148 $err = NET_LDAP2_ERROR;
1149 $msg = $this->errorMessage($err);
1151 return $this->raiseError($msg, $err);
1154 $this->raiseError("Unkown Option requested");
1157 $this->raiseError("No LDAP connection");
1162 * Get the LDAP_PROTOCOL_VERSION that is used on the connection.
1164 * A lot of ldap functionality is defined by what protocol version the ldap server speaks.
1165 * This might be 2 or 3.
1169 public function getLDAPVersion()
1172 $version = $this->getOption("LDAP_OPT_PROTOCOL_VERSION");
1174 $version = $this->_config['version'];
1180 * Set the LDAP_PROTOCOL_VERSION that is used on the connection.
1182 * @param int $version LDAP-version that should be used
1183 * @param boolean $force If set to true, the check against the rootDSE will be skipped
1185 * @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
1186 * @todo Checking via the rootDSE takes much time - why? fetching and instanciation is quick!
1188 public function setLDAPVersion($version = 0, $force = false)
1191 $version = $this->_config['version'];
1195 // Check to see if the server supports this version first.
1197 // Todo: Why is this so horribly slow?
1198 // $this->rootDse() is very fast, as well as Net_LDAP2_RootDSE::fetch()
1199 // seems like a problem at copiyng the object inside PHP??
1200 // Additionally, this is not always reproducable...
1203 $rootDSE = $this->rootDse();
1204 if ($rootDSE instanceof Net_LDAP2_Error) {
1207 $supported_versions = $rootDSE->getValue('supportedLDAPVersion');
1208 if (is_string($supported_versions)) {
1209 $supported_versions = array($supported_versions);
1211 $check_ok = in_array($version, $supported_versions);
1215 if ($force || $check_ok) {
1216 return $this->setOption("LDAP_OPT_PROTOCOL_VERSION", $version);
1218 return $this->raiseError("LDAP Server does not support protocol version " . $version);
1224 * Tells if a DN does exist in the directory
1226 * @param string|Net_LDAP2_Entry $dn The DN of the object to test
1228 * @return boolean|Net_LDAP2_Error
1230 public function dnExists($dn)
1232 if (PEAR::isError($dn)) {
1235 if ($dn instanceof Net_LDAP2_Entry) {
1238 if (false === is_string($dn)) {
1239 return PEAR::raiseError('Parameter $dn is not a string nor an entry object!');
1242 // make dn relative to parent
1243 $base = Net_LDAP2_Util::ldap_explode_dn($dn, array('casefold' => 'none', 'reverse' => false, 'onlyvalues' => false));
1244 if (self::isError($base)) {
1247 $entry_rdn = array_shift($base);
1248 if (is_array($entry_rdn)) {
1249 // maybe the dn consist of a multivalued RDN, we must build the dn in this case
1250 // because the $entry_rdn is an array!
1251 $filter_dn = Net_LDAP2_Util::canonical_dn($entry_rdn);
1253 $base = Net_LDAP2_Util::canonical_dn($base);
1255 $result = @ldap_list($this->_link, $base, $entry_rdn, array(), 1, 1);
1256 if (@ldap_count_entries($this->_link, $result)) {
1259 if (ldap_errno($this->_link) == 32) {
1262 if (ldap_errno($this->_link) != 0) {
1263 return PEAR::raiseError(ldap_error($this->_link), ldap_errno($this->_link));
1270 * Get a specific entry based on the DN
1272 * @param string $dn DN of the entry that should be fetched
1273 * @param array $attr Array of Attributes to select. If ommitted, all attributes are fetched.
1275 * @return Net_LDAP2_Entry|Net_LDAP2_Error Reference to a Net_LDAP2_Entry object or Net_LDAP2_Error object
1276 * @todo Maybe check against the shema should be done to be sure the attribute type exists
1278 public function &getEntry($dn, $attr = array())
1280 if (!is_array($attr)) {
1281 $attr = array($attr);
1283 $result = $this->search($dn, '(objectClass=*)',
1284 array('scope' => 'base', 'attributes' => $attr));
1285 if (self::isError($result)) {
1287 } elseif ($result->count() == 0) {
1288 return PEAR::raiseError('Could not fetch entry '.$dn.': no entry found');
1290 $entry = $result->shiftEntry();
1291 if (false == $entry) {
1292 return PEAR::raiseError('Could not fetch entry (error retrieving entry from search result)');
1298 * Rename or move an entry
1300 * This method will instantly carry out an update() after the move,
1301 * so the entry is moved instantly.
1302 * You can pass an optional Net_LDAP2 object. In this case, a cross directory
1303 * move will be performed which deletes the entry in the source (THIS) directory
1304 * and adds it in the directory $target_ldap.
1305 * A cross directory move will switch the Entrys internal LDAP reference so
1306 * updates to the entry will go to the new directory.
1308 * Note that if you want to do a cross directory move, you need to
1309 * pass an Net_LDAP2_Entry object, otherwise the attributes will be empty.
1311 * @param string|Net_LDAP2_Entry $entry Entry DN or Entry object
1312 * @param string $newdn New location
1313 * @param Net_LDAP2 $target_ldap (optional) Target directory for cross server move; should be passed via reference
1315 * @return Net_LDAP2_Error|true
1317 public function move($entry, $newdn, $target_ldap = null)
1319 if (is_string($entry)) {
1320 $entry_o = $this->getEntry($entry);
1324 if (!$entry_o instanceof Net_LDAP2_Entry) {
1325 return PEAR::raiseError('Parameter $entry is expected to be a Net_LDAP2_Entry object! (If DN was passed, conversion failed)');
1327 if (null !== $target_ldap && !$target_ldap instanceof Net_LDAP2) {
1328 return PEAR::raiseError('Parameter $target_ldap is expected to be a Net_LDAP2 object!');
1331 if ($target_ldap && $target_ldap !== $this) {
1332 // cross directory move
1333 if (is_string($entry)) {
1334 return PEAR::raiseError('Unable to perform cross directory move: operation requires a Net_LDAP2_Entry object');
1336 if ($target_ldap->dnExists($newdn)) {
1337 return PEAR::raiseError('Unable to perform cross directory move: entry does exist in target directory');
1339 $entry_o->dn($newdn);
1340 $res = $target_ldap->add($entry_o);
1341 if (self::isError($res)) {
1342 return PEAR::raiseError('Unable to perform cross directory move: '.$res->getMessage().' in target directory');
1344 $res = $this->delete($entry_o->currentDN());
1345 if (self::isError($res)) {
1346 $res2 = $target_ldap->delete($entry_o); // undo add
1347 if (self::isError($res2)) {
1348 $add_error_string = 'Additionally, the deletion (undo add) of $entry in target directory failed.';
1350 return PEAR::raiseError('Unable to perform cross directory move: '.$res->getMessage().' in source directory. '.$add_error_string);
1352 $entry_o->setLDAP($target_ldap);
1356 $entry_o->dn($newdn);
1357 $entry_o->setLDAP($this);
1358 return $entry_o->update();
1363 * Copy an entry to a new location
1365 * The entry will be immediately copied.
1366 * Please note that only attributes you have
1367 * selected will be copied.
1369 * @param Net_LDAP2_Entry &$entry Entry object
1370 * @param string $newdn New FQF-DN of the entry
1372 * @return Net_LDAP2_Error|Net_LDAP2_Entry Error Message or reference to the copied entry
1374 public function ©(&$entry, $newdn)
1376 if (!$entry instanceof Net_LDAP2_Entry) {
1377 return PEAR::raiseError('Parameter $entry is expected to be a Net_LDAP2_Entry object!');
1380 $newentry = Net_LDAP2_Entry::createFresh($newdn, $entry->getValues());
1381 $result = $this->add($newentry);
1383 if ($result instanceof Net_LDAP2_Error) {
1392 * Returns the string for an ldap errorcode.
1394 * Made to be able to make better errorhandling
1395 * Function based on DB::errorMessage()
1396 * Tip: The best description of the errorcodes is found here:
1397 * http://www.directory-info.com/LDAP2/LDAPErrorCodes.html
1399 * @param int $errorcode Error code
1401 * @return string The errorstring for the error.
1403 public function errorMessage($errorcode)
1405 $errorMessages = array(
1406 0x00 => "LDAP_SUCCESS",
1407 0x01 => "LDAP_OPERATIONS_ERROR",
1408 0x02 => "LDAP_PROTOCOL_ERROR",
1409 0x03 => "LDAP_TIMELIMIT_EXCEEDED",
1410 0x04 => "LDAP_SIZELIMIT_EXCEEDED",
1411 0x05 => "LDAP_COMPARE_FALSE",
1412 0x06 => "LDAP_COMPARE_TRUE",
1413 0x07 => "LDAP_AUTH_METHOD_NOT_SUPPORTED",
1414 0x08 => "LDAP_STRONG_AUTH_REQUIRED",
1415 0x09 => "LDAP_PARTIAL_RESULTS",
1416 0x0a => "LDAP_REFERRAL",
1417 0x0b => "LDAP_ADMINLIMIT_EXCEEDED",
1418 0x0c => "LDAP_UNAVAILABLE_CRITICAL_EXTENSION",
1419 0x0d => "LDAP_CONFIDENTIALITY_REQUIRED",
1420 0x0e => "LDAP_SASL_BIND_INPROGRESS",
1421 0x10 => "LDAP_NO_SUCH_ATTRIBUTE",
1422 0x11 => "LDAP_UNDEFINED_TYPE",
1423 0x12 => "LDAP_INAPPROPRIATE_MATCHING",
1424 0x13 => "LDAP_CONSTRAINT_VIOLATION",
1425 0x14 => "LDAP_TYPE_OR_VALUE_EXISTS",
1426 0x15 => "LDAP_INVALID_SYNTAX",
1427 0x20 => "LDAP_NO_SUCH_OBJECT",
1428 0x21 => "LDAP_ALIAS_PROBLEM",
1429 0x22 => "LDAP_INVALID_DN_SYNTAX",
1430 0x23 => "LDAP_IS_LEAF",
1431 0x24 => "LDAP_ALIAS_DEREF_PROBLEM",
1432 0x30 => "LDAP_INAPPROPRIATE_AUTH",
1433 0x31 => "LDAP_INVALID_CREDENTIALS",
1434 0x32 => "LDAP_INSUFFICIENT_ACCESS",
1435 0x33 => "LDAP_BUSY",
1436 0x34 => "LDAP_UNAVAILABLE",
1437 0x35 => "LDAP_UNWILLING_TO_PERFORM",
1438 0x36 => "LDAP_LOOP_DETECT",
1439 0x3C => "LDAP_SORT_CONTROL_MISSING",
1440 0x3D => "LDAP_INDEX_RANGE_ERROR",
1441 0x40 => "LDAP_NAMING_VIOLATION",
1442 0x41 => "LDAP_OBJECT_CLASS_VIOLATION",
1443 0x42 => "LDAP_NOT_ALLOWED_ON_NONLEAF",
1444 0x43 => "LDAP_NOT_ALLOWED_ON_RDN",
1445 0x44 => "LDAP_ALREADY_EXISTS",
1446 0x45 => "LDAP_NO_OBJECT_CLASS_MODS",
1447 0x46 => "LDAP_RESULTS_TOO_LARGE",
1448 0x47 => "LDAP_AFFECTS_MULTIPLE_DSAS",
1449 0x50 => "LDAP_OTHER",
1450 0x51 => "LDAP_SERVER_DOWN",
1451 0x52 => "LDAP_LOCAL_ERROR",
1452 0x53 => "LDAP_ENCODING_ERROR",
1453 0x54 => "LDAP_DECODING_ERROR",
1454 0x55 => "LDAP_TIMEOUT",
1455 0x56 => "LDAP_AUTH_UNKNOWN",
1456 0x57 => "LDAP_FILTER_ERROR",
1457 0x58 => "LDAP_USER_CANCELLED",
1458 0x59 => "LDAP_PARAM_ERROR",
1459 0x5a => "LDAP_NO_MEMORY",
1460 0x5b => "LDAP_CONNECT_ERROR",
1461 0x5c => "LDAP_NOT_SUPPORTED",
1462 0x5d => "LDAP_CONTROL_NOT_FOUND",
1463 0x5e => "LDAP_NO_RESULTS_RETURNED",
1464 0x5f => "LDAP_MORE_RESULTS_TO_RETURN",
1465 0x60 => "LDAP_CLIENT_LOOP",
1466 0x61 => "LDAP_REFERRAL_LIMIT_EXCEEDED",
1467 1000 => "Unknown Net_LDAP2 Error"
1470 return isset($errorMessages[$errorcode]) ?
1471 $errorMessages[$errorcode] :
1472 $errorMessages[NET_LDAP2_ERROR] . ' (' . $errorcode . ')';
1476 * Gets a rootDSE object
1478 * This either fetches a fresh rootDSE object or returns it from
1479 * the internal cache for performance reasons, if possible.
1481 * @param array $attrs Array of attributes to search for
1484 * @return Net_LDAP2_Error|Net_LDAP2_RootDSE Net_LDAP2_Error or Net_LDAP2_RootDSE object
1486 public function &rootDse($attrs = null)
1488 if ($attrs !== null && !is_array($attrs)) {
1489 return PEAR::raiseError('Parameter $attr is expected to be an array!');
1492 $attrs_signature = serialize($attrs);
1494 // see if we need to fetch a fresh object, or if we already
1495 // requested this object with the same attributes
1496 if (true || !array_key_exists($attrs_signature, $this->_rootDSE_cache)) {
1497 $rootdse =& Net_LDAP2_RootDSE::fetch($this, $attrs);
1498 if ($rootdse instanceof Net_LDAP2_Error) {
1502 // search was ok, store rootDSE in cache
1503 $this->_rootDSE_cache[$attrs_signature] = $rootdse;
1505 return $this->_rootDSE_cache[$attrs_signature];
1509 * Alias function of rootDse() for perl-ldap interface
1513 * @return Net_LDAP2_Error|Net_LDAP2_RootDSE
1515 public function &root_dse()
1517 $args = func_get_args();
1518 return call_user_func_array(array(&$this, 'rootDse'), $args);
1522 * Get a schema object
1524 * @param string $dn (optional) Subschema entry dn
1527 * @return Net_LDAP2_Schema|Net_LDAP2_Error Net_LDAP2_Schema or Net_LDAP2_Error object
1529 public function &schema($dn = null)
1531 // Schema caching by Knut-Olav Hoven
1532 // If a schema caching object is registered, we use that to fetch
1534 // See registerSchemaCache() for more info on this.
1535 if ($this->_schema === null) {
1536 if ($this->_schema_cache) {
1537 $cached_schema = $this->_schema_cache->loadSchema();
1538 if ($cached_schema instanceof Net_LDAP2_Error) {
1539 return $cached_schema; // route error to client
1541 if ($cached_schema instanceof Net_LDAP2_Schema) {
1542 $this->_schema = $cached_schema;
1548 // Fetch schema, if not tried before and no cached version available.
1549 // If we are already fetching the schema, we will skip fetching.
1550 if ($this->_schema === null) {
1551 // store a temporary error message so subsequent calls to schema() can
1552 // detect, that we are fetching the schema already.
1553 // Otherwise we will get an infinite loop at Net_LDAP2_Schema::fetch()
1554 $this->_schema = new Net_LDAP2_Error('Schema not initialized');
1555 $this->_schema = Net_LDAP2_Schema::fetch($this, $dn);
1557 // If schema caching is active, advise the cache to store the schema
1558 if ($this->_schema_cache) {
1559 $caching_result = $this->_schema_cache->storeSchema($this->_schema);
1560 if ($caching_result instanceof Net_LDAP2_Error) {
1561 return $caching_result; // route error to client
1565 return $this->_schema;
1569 * Enable/disable persistent schema caching
1571 * Sometimes it might be useful to allow your scripts to cache
1572 * the schema information on disk, so the schema is not fetched
1573 * every time the script runs which could make your scripts run
1576 * This method allows you to register a custom object that
1577 * implements your schema cache. Please see the SchemaCache interface
1578 * (SchemaCache.interface.php) for informations on how to implement this.
1579 * To unregister the cache, pass null as $cache parameter.
1581 * For ease of use, Net_LDAP2 provides a simple file based cache
1582 * which is used in the example below. You may use this, for example,
1583 * to store the schema in a linux tmpfs which results in the schema
1584 * beeing cached inside the RAM which allows nearly instant access.
1586 * // Create the simple file cache object that comes along with Net_LDAP2
1587 * $mySchemaCache_cfg = array(
1588 * 'path' => '/tmp/Net_LDAP2_Schema.cache',
1589 * 'max_age' => 86400 // max age is 24 hours (in seconds)
1591 * $mySchemaCache = new Net_LDAP2_SimpleFileSchemaCache($mySchemaCache_cfg);
1592 * $ldap = new Net_LDAP2::connect(...);
1593 * $ldap->registerSchemaCache($mySchemaCache); // enable caching
1594 * // now each call to $ldap->schema() will get the schema from disk!
1597 * @param Net_LDAP2_SchemaCache|null $cache Object implementing the Net_LDAP2_SchemaCache interface
1599 * @return true|Net_LDAP2_Error
1601 public function registerSchemaCache($cache) {
1603 || (is_object($cache) && in_array('Net_LDAP2_SchemaCache', class_implements($cache))) ) {
1604 $this->_schema_cache = $cache;
1607 return new Net_LDAP2_Error('Custom schema caching object is either no '.
1608 'valid object or does not implement the Net_LDAP2_SchemaCache interface!');
1614 * Checks if phps ldap-extension is loaded
1616 * If it is not loaded, it tries to load it manually using PHPs dl().
1617 * It knows both windows-dll and *nix-so.
1620 * @return Net_LDAP2_Error|true
1622 public static function checkLDAPExtension()
1624 if (!extension_loaded('ldap') && !@dl('ldap.' . PHP_SHLIB_SUFFIX)) {
1625 return new Net_LDAP2_Error("It seems that you do not have the ldap-extension installed. Please install it before using the Net_LDAP2 package.");
1632 * Encodes given attributes to UTF8 if needed by schema
1634 * This function takes attributes in an array and then checks against the schema if they need
1635 * UTF8 encoding. If that is so, they will be encoded. An encoded array will be returned and
1636 * can be used for adding or modifying.
1638 * $attributes is expected to be an array with keys describing
1639 * the attribute names and the values as the value of this attribute:
1640 * <code>$attributes = array('cn' => 'foo', 'attr2' => array('mv1', 'mv2'));</code>
1642 * @param array $attributes Array of attributes
1645 * @return array|Net_LDAP2_Error Array of UTF8 encoded attributes or Error
1647 public function utf8Encode($attributes)
1649 return $this->utf8($attributes, 'utf8_encode');
1653 * Decodes the given attribute values if needed by schema
1655 * $attributes is expected to be an array with keys describing
1656 * the attribute names and the values as the value of this attribute:
1657 * <code>$attributes = array('cn' => 'foo', 'attr2' => array('mv1', 'mv2'));</code>
1659 * @param array $attributes Array of attributes
1663 * @return array|Net_LDAP2_Error Array with decoded attribute values or Error
1665 public function utf8Decode($attributes)
1667 return $this->utf8($attributes, 'utf8_decode');
1671 * Encodes or decodes attribute values if needed
1673 * @param array $attributes Array of attributes
1674 * @param array $function Function to apply to attribute values
1677 * @return array|Net_LDAP2_Error Array of attributes with function applied to values or Error
1679 protected function utf8($attributes, $function)
1681 if (!is_array($attributes) || array_key_exists(0, $attributes)) {
1682 return PEAR::raiseError('Parameter $attributes is expected to be an associative array');
1685 if (!$this->_schema) {
1686 $this->_schema = $this->schema();
1689 if (!$this->_link || self::isError($this->_schema) || !function_exists($function)) {
1693 if (is_array($attributes) && count($attributes) > 0) {
1695 foreach ($attributes as $k => $v) {
1697 if (!isset($this->_schemaAttrs[$k])) {
1699 $attr = $this->_schema->get('attribute', $k);
1700 if (self::isError($attr)) {
1704 if (false !== strpos($attr['syntax'], '1.3.6.1.4.1.1466.115.121.1.15')) {
1709 $this->_schemaAttrs[$k] = $encode;
1712 $encode = $this->_schemaAttrs[$k];
1717 foreach ($v as $ak => $av) {
1718 $v[$ak] = call_user_func($function, $av);
1721 $v = call_user_func($function, $v);
1724 $attributes[$k] = $v;
1731 * Get the LDAP link resource. It will loop attempting to
1732 * re-establish the connection if the connection attempt fails and
1733 * auto_reconnect has been turned on (see the _config array documentation).
1736 * @return resource LDAP link
1738 public function &getLink()
1740 if ($this->_config['auto_reconnect']) {
1743 // Return the link handle if we are already connected. Otherwise
1744 // try to reconnect.
1746 if ($this->_link !== false) {
1747 return $this->_link;
1749 $this->performReconnect();
1753 return $this->_link;
1758 * Net_LDAP2_Error implements a class for reporting portable LDAP error messages.
1761 * @package Net_LDAP2
1762 * @author Tarjej Huse <tarjei@bergfald.no>
1763 * @license http://www.gnu.org/copyleft/lesser.html LGPL
1764 * @link http://pear.php.net/package/Net_LDAP22/
1766 class Net_LDAP2_Error extends PEAR_Error
1769 * Net_LDAP2_Error constructor.
1771 * @param string $message String with error message.
1772 * @param integer $code Net_LDAP2 error code
1773 * @param integer $mode what "error mode" to operate in
1774 * @param mixed $level what error level to use for $mode & PEAR_ERROR_TRIGGER
1775 * @param mixed $debuginfo additional debug info, such as the last query
1780 public function __construct($message = 'Net_LDAP2_Error', $code = NET_LDAP2_ERROR, $mode = PEAR_ERROR_RETURN,
1781 $level = E_USER_NOTICE, $debuginfo = null)
1783 if (is_int($code)) {
1784 $this->PEAR_Error($message . ': ' . Net_LDAP2::errorMessage($code), $code, $mode, $level, $debuginfo);
1786 $this->PEAR_Error("$message: $code", NET_LDAP2_ERROR, $mode, $level, $debuginfo);