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 332308 2013-12-09 09:15:47Z 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.1.0');
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 Note, that not all servers allow to feth either the rootDSE or
619 attributes over an unencrypted channel, so we must ignore errors. */
620 $rootDSE = $this->rootDse();
621 if (self::isError($rootDSE)) {
622 /* IGNORE this error, because server may refuse fetching the
623 RootDSE over an unencrypted connection. */
624 //return $this->raiseError("Unable to fetch rootDSE entry ".
625 //"to see if TLS is supoported: ".$rootDSE->getMessage(), $rootDSE->getCode());
627 /* Fetch suceeded, see, if the server supports TLS. Again, we
628 ignore errors, because the server may refuse to return
629 attributes over unencryted connections. */
630 $supported_extensions = $rootDSE->getValue('supportedExtension');
631 if (self::isError($supported_extensions)) {
632 /* IGNORE error, because server may refuse attribute
633 returning over an unencrypted connection. */
634 //return $this->raiseError("Unable to fetch rootDSE attribute 'supportedExtension' ".
635 //"to see if TLS is supoported: ".$supported_extensions->getMessage(), $supported_extensions->getCode());
637 // fetch succeedet, lets see if the server supports it.
638 // if not, then drop an error. If supported, then do nothing,
639 // because then we try to issue TLS afterwards.
640 if (!in_array('1.3.6.1.4.1.1466.20037', $supported_extensions)) {
641 return $this->raiseError("Server reports that it does not support TLS.");
646 // Try to establish TLS.
647 if (false === @ldap_start_tls($this->_link)) {
648 // Starting TLS failed. This may be an error, or because
649 // the server does not support it but did not enable us to
650 // detect that above.
651 return $this->raiseError("TLS could not be started: " .
652 @ldap_error($this->_link),
653 @ldap_errno($this->_link));
655 return true; // TLS is started now.
660 * alias function of startTLS() for perl-ldap interface
665 public function start_tls()
667 $args = func_get_args();
668 return call_user_func_array(array( &$this, 'startTLS' ), $args);
672 * Close LDAP connection.
674 * Closes the connection. Use this when the session is over.
678 public function done()
684 * Alias for {@link done()}
689 public function disconnect()
699 public function _Net_LDAP2()
701 @ldap_close($this->_link);
705 * Add a new entryobject to a directory.
707 * Use add to add a new Net_LDAP2_Entry object to the directory.
708 * This also links the entry to the connection used for the add,
709 * if it was a fresh entry ({@link Net_LDAP2_Entry::createFresh()})
711 * @param Net_LDAP2_Entry &$entry Net_LDAP2_Entry
713 * @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
715 public function add(&$entry)
717 if (!$entry instanceof Net_LDAP2_Entry) {
718 return PEAR::raiseError('Parameter to Net_LDAP2::add() must be a Net_LDAP2_Entry object.');
721 // Continue attempting the add operation in a loop until we
722 // get a success, a definitive failure, or the world ends.
725 $link = $this->getLink();
727 if ($link === false) {
728 // We do not have a successful connection yet. The call to
729 // getLink() would have kept trying if we wanted one. Go
731 return PEAR::raiseError("Could not add entry " . $entry->dn() .
732 " no valid LDAP connection could be found.");
735 if (@ldap_add($link, $entry->dn(), $entry->getValues())) {
736 // entry successfully added, we should update its $ldap reference
737 // in case it is not set so far (fresh entry)
738 if (!$entry->getLDAP() instanceof Net_LDAP2) {
739 $entry->setLDAP($this);
741 // store, that the entry is present inside the directory
742 $entry->markAsNew(false);
745 // We have a failure. What type? We may be able to reconnect
747 $error_code = @ldap_errno($link);
748 $error_name = Net_LDAP2::errorMessage($error_code);
750 if (($error_name === 'LDAP_OPERATIONS_ERROR') &&
751 ($this->_config['auto_reconnect'])) {
753 // The server has become disconnected before trying the
754 // operation. We should try again, possibly with a different
756 $this->_link = false;
757 $this->performReconnect();
759 // Errors other than the above catched are just passed
760 // back to the user so he may react upon them.
761 return PEAR::raiseError("Could not add entry " . $entry->dn() . " " .
770 * Delete an entry from the directory
772 * The object may either be a string representing the dn or a Net_LDAP2_Entry
773 * object. When the boolean paramter recursive is set, all subentries of the
774 * entry will be deleted as well.
776 * @param string|Net_LDAP2_Entry $dn DN-string or Net_LDAP2_Entry
777 * @param boolean $recursive Should we delete all children recursive as well?
780 * @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
782 public function delete($dn, $recursive = false)
784 if ($dn instanceof Net_LDAP2_Entry) {
787 if (false === is_string($dn)) {
788 return PEAR::raiseError("Parameter is not a string nor an entry object!");
790 // Recursive delete searches for children and calls delete for them
792 $result = @ldap_list($this->_link, $dn, '(objectClass=*)', array(null), 0, 0);
793 if (@ldap_count_entries($this->_link, $result)) {
794 $subentry = @ldap_first_entry($this->_link, $result);
795 $this->delete(@ldap_get_dn($this->_link, $subentry), true);
796 while ($subentry = @ldap_next_entry($this->_link, $subentry)) {
797 $this->delete(@ldap_get_dn($this->_link, $subentry), true);
802 // Continue attempting the delete operation in a loop until we
803 // get a success, a definitive failure, or the world ends.
805 $link = $this->getLink();
807 if ($link === false) {
808 // We do not have a successful connection yet. The call to
809 // getLink() would have kept trying if we wanted one. Go
811 return PEAR::raiseError("Could not add entry " . $dn .
812 " no valid LDAP connection could be found.");
815 if (@ldap_delete($link, $dn)) {
816 // entry successfully deleted.
819 // We have a failure. What type?
820 // We may be able to reconnect and try again.
821 $error_code = @ldap_errno($link);
822 $error_name = Net_LDAP2::errorMessage($error_code);
824 if ((Net_LDAP2::errorMessage($error_code) === 'LDAP_OPERATIONS_ERROR') &&
825 ($this->_config['auto_reconnect'])) {
826 // The server has become disconnected before trying the
827 // operation. We should try again, possibly with a
829 $this->_link = false;
830 $this->performReconnect();
832 } elseif ($error_code == 66) {
833 // Subentries present, server refused to delete.
834 // Deleting subentries is the clients responsibility, but
835 // since the user may not know of the subentries, we do not
836 // force that here but instead notify the developer so he
837 // may take actions himself.
838 return PEAR::raiseError("Could not delete entry $dn because of subentries. Use the recursive parameter to delete them.");
841 // Errors other than the above catched are just passed
842 // back to the user so he may react upon them.
843 return PEAR::raiseError("Could not delete entry " . $dn . " " .
852 * Modify an ldapentry directly on the server
854 * This one takes the DN or a Net_LDAP2_Entry object and an array of actions.
855 * This array should be something like this:
857 * array('add' => array('attribute1' => array('val1', 'val2'),
858 * 'attribute2' => array('val1')),
859 * 'delete' => array('attribute1'),
860 * 'replace' => array('attribute1' => array('val1')),
861 * 'changes' => array('add' => ...,
863 * 'delete' => array('attribute1', 'attribute2' => array('val1')))
865 * The changes array is there so the order of operations can be influenced
866 * (the operations are done in order of appearance).
867 * The order of execution is as following:
868 * 1. adds from 'add' array
869 * 2. deletes from 'delete' array
870 * 3. replaces from 'replace' array
871 * 4. changes (add, replace, delete) in order of appearance
872 * All subarrays (add, replace, delete, changes) may be given at the same time.
874 * The function calls the corresponding functions of an Net_LDAP2_Entry
875 * object. A detailed description of array structures can be found there.
877 * Unlike the modification methods provided by the Net_LDAP2_Entry object,
878 * this method will instantly carry out an update() after each operation,
879 * thus modifying "directly" on the server.
881 * @param string|Net_LDAP2_Entry $entry DN-string or Net_LDAP2_Entry
882 * @param array $parms Array of changes
885 * @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
887 public function modify($entry, $parms = array())
889 if (is_string($entry)) {
890 $entry = $this->getEntry($entry);
891 if (self::isError($entry)) {
895 if (!$entry instanceof Net_LDAP2_Entry) {
896 return PEAR::raiseError("Parameter is not a string nor an entry object!");
899 // Perform changes mentioned separately
900 foreach (array('add', 'delete', 'replace') as $action) {
901 if (isset($parms[$action])) {
902 $msg = $entry->$action($parms[$action]);
903 if (self::isError($msg)) {
906 $entry->setLDAP($this);
908 // Because the @ldap functions are called inside Net_LDAP2_Entry::update(),
909 // we have to trap the error codes issued from that if we want to support
912 $msg = $entry->update();
914 if (self::isError($msg)) {
915 // We have a failure. What type? We may be able to reconnect
917 $error_code = $msg->getCode();
918 $error_name = Net_LDAP2::errorMessage($error_code);
920 if ((Net_LDAP2::errorMessage($error_code) === 'LDAP_OPERATIONS_ERROR') &&
921 ($this->_config['auto_reconnect'])) {
923 // The server has become disconnected before trying the
924 // operation. We should try again, possibly with a different
926 $this->_link = false;
927 $this->performReconnect();
931 // Errors other than the above catched are just passed
932 // back to the user so he may react upon them.
933 return PEAR::raiseError("Could not modify entry: ".$msg->getMessage());
936 // modification succeedet, evaluate next change
943 // perform combined changes in 'changes' array
944 if (isset($parms['changes']) && is_array($parms['changes'])) {
945 foreach ($parms['changes'] as $action => $value) {
947 // Because the @ldap functions are called inside Net_LDAP2_Entry::update,
948 // we have to trap the error codes issued from that if we want to support
951 $msg = $this->modify($entry, array($action => $value));
953 if (self::isError($msg)) {
954 // We have a failure. What type? We may be able to reconnect
956 $error_code = $msg->getCode();
957 $error_name = Net_LDAP2::errorMessage($error_code);
959 if ((Net_LDAP2::errorMessage($error_code) === 'LDAP_OPERATIONS_ERROR') &&
960 ($this->_config['auto_reconnect'])) {
962 // The server has become disconnected before trying the
963 // operation. We should try again, possibly with a different
965 $this->_link = false;
966 $this->performReconnect();
969 // Errors other than the above catched are just passed
970 // back to the user so he may react upon them.
974 // modification succeedet, evaluate next change
985 * Run a ldap search query
987 * Search is used to query the ldap-database.
988 * $base and $filter may be ommitted. The one from config will
989 * then be used. $base is either a DN-string or an Net_LDAP2_Entry
990 * object in which case its DN willb e used.
992 * Params may contain:
994 * scope: The scope which will be used for searching
995 * base - Just one entry
996 * sub - The whole tree
997 * one - Immediately below $base
998 * sizelimit: Limit the number of entries returned (default: 0 = unlimited),
999 * timelimit: Limit the time spent for searching (default: 0 = unlimited),
1000 * attrsonly: If true, the search will only return the attribute names,
1001 * attributes: Array of attribute names, which the entry should contain.
1002 * It is good practice to limit this to just the ones you need.
1004 * deref: By default aliases are dereferenced to locate the base object for the search, but not when
1005 * searching subordinates of the base object. This may be changed by specifying one of the
1008 * never - Do not dereference aliases in searching or in locating the base object of the search.
1009 * search - Dereference aliases in subordinates of the base object in searching, but not in
1010 * locating the base object of the search.
1014 * Please note, that you cannot override server side limitations to sizelimit
1015 * and timelimit: You can always only lower a given limit.
1017 * @param string|Net_LDAP2_Entry $base LDAP searchbase
1018 * @param string|Net_LDAP2_Filter $filter LDAP search filter or a Net_LDAP2_Filter object
1019 * @param array $params Array of options
1022 * @return Net_LDAP2_Search|Net_LDAP2_Error Net_LDAP2_Search object or Net_LDAP2_Error object
1023 * @todo implement search controls (sorting etc)
1025 public function search($base = null, $filter = null, $params = array())
1027 if (is_null($base)) {
1028 $base = $this->_config['basedn'];
1030 if ($base instanceof Net_LDAP2_Entry) {
1031 $base = $base->dn(); // fetch DN of entry, making searchbase relative to the entry
1033 if (is_null($filter)) {
1034 $filter = $this->_config['filter'];
1036 if ($filter instanceof Net_LDAP2_Filter) {
1037 $filter = $filter->asString(); // convert Net_LDAP2_Filter to string representation
1039 if (PEAR::isError($filter)) {
1042 if (PEAR::isError($base)) {
1046 /* setting searchparameters */
1047 (isset($params['sizelimit'])) ? $sizelimit = $params['sizelimit'] : $sizelimit = 0;
1048 (isset($params['timelimit'])) ? $timelimit = $params['timelimit'] : $timelimit = 0;
1049 (isset($params['attrsonly'])) ? $attrsonly = $params['attrsonly'] : $attrsonly = 0;
1050 (isset($params['attributes'])) ? $attributes = $params['attributes'] : $attributes = array();
1052 // Ensure $attributes to be an array in case only one
1053 // attribute name was given as string
1054 if (!is_array($attributes)) {
1055 $attributes = array($attributes);
1058 // reorganize the $attributes array index keys
1059 // sometimes there are problems with not consecutive indexes
1060 $attributes = array_values($attributes);
1062 // scoping makes searches faster!
1063 $scope = (isset($params['scope']) ? $params['scope'] : $this->_config['scope']);
1067 $search_function = 'ldap_list';
1070 $search_function = 'ldap_read';
1073 $search_function = 'ldap_search';
1076 // Continue attempting the search operation until we get a success
1077 // or a definitive failure.
1079 $link = $this->getLink();
1080 $search = @call_user_func($search_function,
1089 if ($err = @ldap_errno($link)) {
1091 // Errorcode 32 = no such object, i.e. a nullresult.
1092 return $obj = new Net_LDAP2_Search ($search, $this, $attributes);
1093 } elseif ($err == 4) {
1094 // Errorcode 4 = sizelimit exeeded.
1095 return $obj = new Net_LDAP2_Search ($search, $this, $attributes);
1096 } elseif ($err == 87) {
1097 // bad search filter
1098 return $this->raiseError(Net_LDAP2::errorMessage($err) . "($filter)", $err);
1099 } elseif (($err == 1) && ($this->_config['auto_reconnect'])) {
1100 // Errorcode 1 = LDAP_OPERATIONS_ERROR but we can try a reconnect.
1101 $this->_link = false;
1102 $this->performReconnect();
1104 $msg = "\nParameters:\nBase: $base\nFilter: $filter\nScope: $scope";
1105 return $this->raiseError(Net_LDAP2::errorMessage($err) . $msg, $err);
1108 return $obj = new Net_LDAP2_Search($search, $this, $attributes);
1114 * Set an LDAP option
1116 * @param string $option Option to set
1117 * @param mixed $value Value to set Option to
1120 * @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
1122 public function setOption($option, $value)
1125 if (defined($option)) {
1126 if (@ldap_set_option($this->_link, constant($option), $value)) {
1129 $err = @ldap_errno($this->_link);
1131 $msg = @ldap_err2str($err);
1133 $err = NET_LDAP2_ERROR;
1134 $msg = Net_LDAP2::errorMessage($err);
1136 return $this->raiseError($msg, $err);
1139 return $this->raiseError("Unkown Option requested");
1142 return $this->raiseError("Could not set LDAP option: No LDAP connection");
1147 * Get an LDAP option value
1149 * @param string $option Option to get
1152 * @return Net_LDAP2_Error|string Net_LDAP2_Error or option value
1154 public function getOption($option)
1157 if (defined($option)) {
1158 if (@ldap_get_option($this->_link, constant($option), $value)) {
1161 $err = @ldap_errno($this->_link);
1163 $msg = @ldap_err2str($err);
1165 $err = NET_LDAP2_ERROR;
1166 $msg = Net_LDAP2::errorMessage($err);
1168 return $this->raiseError($msg, $err);
1171 $this->raiseError("Unkown Option requested");
1174 $this->raiseError("No LDAP connection");
1179 * Get the LDAP_PROTOCOL_VERSION that is used on the connection.
1181 * A lot of ldap functionality is defined by what protocol version the ldap server speaks.
1182 * This might be 2 or 3.
1186 public function getLDAPVersion()
1189 $version = $this->getOption("LDAP_OPT_PROTOCOL_VERSION");
1191 $version = $this->_config['version'];
1197 * Set the LDAP_PROTOCOL_VERSION that is used on the connection.
1199 * @param int $version LDAP-version that should be used
1200 * @param boolean $force If set to true, the check against the rootDSE will be skipped
1202 * @return Net_LDAP2_Error|true Net_LDAP2_Error object or true
1203 * @todo Checking via the rootDSE takes much time - why? fetching and instanciation is quick!
1205 public function setLDAPVersion($version = 0, $force = false)
1208 $version = $this->_config['version'];
1212 // Check to see if the server supports this version first.
1214 // Todo: Why is this so horribly slow?
1215 // $this->rootDse() is very fast, as well as Net_LDAP2_RootDSE::fetch()
1216 // seems like a problem at copiyng the object inside PHP??
1217 // Additionally, this is not always reproducable...
1220 $rootDSE = $this->rootDse();
1221 if ($rootDSE instanceof Net_LDAP2_Error) {
1224 $supported_versions = $rootDSE->getValue('supportedLDAPVersion');
1225 if (is_string($supported_versions)) {
1226 $supported_versions = array($supported_versions);
1228 $check_ok = in_array($version, $supported_versions);
1232 if ($force || $check_ok) {
1233 return $this->setOption("LDAP_OPT_PROTOCOL_VERSION", $version);
1235 return $this->raiseError("LDAP Server does not support protocol version " . $version);
1241 * Tells if a DN does exist in the directory
1243 * @param string|Net_LDAP2_Entry $dn The DN of the object to test
1245 * @return boolean|Net_LDAP2_Error
1247 public function dnExists($dn)
1249 if (PEAR::isError($dn)) {
1252 if ($dn instanceof Net_LDAP2_Entry) {
1255 if (false === is_string($dn)) {
1256 return PEAR::raiseError('Parameter $dn is not a string nor an entry object!');
1259 // search LDAP for that DN by performing a baselevel search for any
1260 // object. We can only find the DN in question this way, or nothing.
1264 'attributes' => '1.1' // select no attrs
1266 $search = $this->search($dn, '(objectClass=*)', $s_opts);
1268 if (self::isError($search)) {
1272 // retun wehter the DN exists; that is, we found an entry
1273 return ($search->count() == 0)? false : true;
1278 * Get a specific entry based on the DN
1280 * @param string $dn DN of the entry that should be fetched
1281 * @param array $attr Array of Attributes to select. If ommitted, all attributes are fetched.
1283 * @return Net_LDAP2_Entry|Net_LDAP2_Error Reference to a Net_LDAP2_Entry object or Net_LDAP2_Error object
1284 * @todo Maybe check against the shema should be done to be sure the attribute type exists
1286 public function &getEntry($dn, $attr = array())
1288 if (!is_array($attr)) {
1289 $attr = array($attr);
1291 $result = $this->search($dn, '(objectClass=*)',
1292 array('scope' => 'base', 'attributes' => $attr));
1293 if (self::isError($result)) {
1295 } elseif ($result->count() == 0) {
1296 return PEAR::raiseError('Could not fetch entry '.$dn.': no entry found');
1298 $entry = $result->shiftEntry();
1299 if (false == $entry) {
1300 return PEAR::raiseError('Could not fetch entry (error retrieving entry from search result)');
1306 * Rename or move an entry
1308 * This method will instantly carry out an update() after the move,
1309 * so the entry is moved instantly.
1310 * You can pass an optional Net_LDAP2 object. In this case, a cross directory
1311 * move will be performed which deletes the entry in the source (THIS) directory
1312 * and adds it in the directory $target_ldap.
1313 * A cross directory move will switch the Entrys internal LDAP reference so
1314 * updates to the entry will go to the new directory.
1316 * Note that if you want to do a cross directory move, you need to
1317 * pass an Net_LDAP2_Entry object, otherwise the attributes will be empty.
1319 * @param string|Net_LDAP2_Entry $entry Entry DN or Entry object
1320 * @param string $newdn New location
1321 * @param Net_LDAP2 $target_ldap (optional) Target directory for cross server move; should be passed via reference
1323 * @return Net_LDAP2_Error|true
1325 public function move($entry, $newdn, $target_ldap = null)
1327 if (is_string($entry)) {
1328 $entry_o = $this->getEntry($entry);
1332 if (!$entry_o instanceof Net_LDAP2_Entry) {
1333 return PEAR::raiseError('Parameter $entry is expected to be a Net_LDAP2_Entry object! (If DN was passed, conversion failed)');
1335 if (null !== $target_ldap && !$target_ldap instanceof Net_LDAP2) {
1336 return PEAR::raiseError('Parameter $target_ldap is expected to be a Net_LDAP2 object!');
1339 if ($target_ldap && $target_ldap !== $this) {
1340 // cross directory move
1341 if (is_string($entry)) {
1342 return PEAR::raiseError('Unable to perform cross directory move: operation requires a Net_LDAP2_Entry object');
1344 if ($target_ldap->dnExists($newdn)) {
1345 return PEAR::raiseError('Unable to perform cross directory move: entry does exist in target directory');
1347 $entry_o->dn($newdn);
1348 $res = $target_ldap->add($entry_o);
1349 if (self::isError($res)) {
1350 return PEAR::raiseError('Unable to perform cross directory move: '.$res->getMessage().' in target directory');
1352 $res = $this->delete($entry_o->currentDN());
1353 if (self::isError($res)) {
1354 $res2 = $target_ldap->delete($entry_o); // undo add
1355 if (self::isError($res2)) {
1356 $add_error_string = 'Additionally, the deletion (undo add) of $entry in target directory failed.';
1358 return PEAR::raiseError('Unable to perform cross directory move: '.$res->getMessage().' in source directory. '.$add_error_string);
1360 $entry_o->setLDAP($target_ldap);
1364 $entry_o->dn($newdn);
1365 $entry_o->setLDAP($this);
1366 return $entry_o->update();
1371 * Copy an entry to a new location
1373 * The entry will be immediately copied.
1374 * Please note that only attributes you have
1375 * selected will be copied.
1377 * @param Net_LDAP2_Entry &$entry Entry object
1378 * @param string $newdn New FQF-DN of the entry
1380 * @return Net_LDAP2_Error|Net_LDAP2_Entry Error Message or reference to the copied entry
1382 public function ©(&$entry, $newdn)
1384 if (!$entry instanceof Net_LDAP2_Entry) {
1385 return PEAR::raiseError('Parameter $entry is expected to be a Net_LDAP2_Entry object!');
1388 $newentry = Net_LDAP2_Entry::createFresh($newdn, $entry->getValues());
1389 $result = $this->add($newentry);
1391 if ($result instanceof Net_LDAP2_Error) {
1400 * Returns the string for an ldap errorcode.
1402 * Made to be able to make better errorhandling
1403 * Function based on DB::errorMessage()
1404 * Tip: The best description of the errorcodes is found here:
1405 * http://www.directory-info.com/LDAP2/LDAPErrorCodes.html
1407 * @param int $errorcode Error code
1409 * @return string The errorstring for the error.
1411 public static function errorMessage($errorcode)
1413 $errorMessages = array(
1414 0x00 => "LDAP_SUCCESS",
1415 0x01 => "LDAP_OPERATIONS_ERROR",
1416 0x02 => "LDAP_PROTOCOL_ERROR",
1417 0x03 => "LDAP_TIMELIMIT_EXCEEDED",
1418 0x04 => "LDAP_SIZELIMIT_EXCEEDED",
1419 0x05 => "LDAP_COMPARE_FALSE",
1420 0x06 => "LDAP_COMPARE_TRUE",
1421 0x07 => "LDAP_AUTH_METHOD_NOT_SUPPORTED",
1422 0x08 => "LDAP_STRONG_AUTH_REQUIRED",
1423 0x09 => "LDAP_PARTIAL_RESULTS",
1424 0x0a => "LDAP_REFERRAL",
1425 0x0b => "LDAP_ADMINLIMIT_EXCEEDED",
1426 0x0c => "LDAP_UNAVAILABLE_CRITICAL_EXTENSION",
1427 0x0d => "LDAP_CONFIDENTIALITY_REQUIRED",
1428 0x0e => "LDAP_SASL_BIND_INPROGRESS",
1429 0x10 => "LDAP_NO_SUCH_ATTRIBUTE",
1430 0x11 => "LDAP_UNDEFINED_TYPE",
1431 0x12 => "LDAP_INAPPROPRIATE_MATCHING",
1432 0x13 => "LDAP_CONSTRAINT_VIOLATION",
1433 0x14 => "LDAP_TYPE_OR_VALUE_EXISTS",
1434 0x15 => "LDAP_INVALID_SYNTAX",
1435 0x20 => "LDAP_NO_SUCH_OBJECT",
1436 0x21 => "LDAP_ALIAS_PROBLEM",
1437 0x22 => "LDAP_INVALID_DN_SYNTAX",
1438 0x23 => "LDAP_IS_LEAF",
1439 0x24 => "LDAP_ALIAS_DEREF_PROBLEM",
1440 0x30 => "LDAP_INAPPROPRIATE_AUTH",
1441 0x31 => "LDAP_INVALID_CREDENTIALS",
1442 0x32 => "LDAP_INSUFFICIENT_ACCESS",
1443 0x33 => "LDAP_BUSY",
1444 0x34 => "LDAP_UNAVAILABLE",
1445 0x35 => "LDAP_UNWILLING_TO_PERFORM",
1446 0x36 => "LDAP_LOOP_DETECT",
1447 0x3C => "LDAP_SORT_CONTROL_MISSING",
1448 0x3D => "LDAP_INDEX_RANGE_ERROR",
1449 0x40 => "LDAP_NAMING_VIOLATION",
1450 0x41 => "LDAP_OBJECT_CLASS_VIOLATION",
1451 0x42 => "LDAP_NOT_ALLOWED_ON_NONLEAF",
1452 0x43 => "LDAP_NOT_ALLOWED_ON_RDN",
1453 0x44 => "LDAP_ALREADY_EXISTS",
1454 0x45 => "LDAP_NO_OBJECT_CLASS_MODS",
1455 0x46 => "LDAP_RESULTS_TOO_LARGE",
1456 0x47 => "LDAP_AFFECTS_MULTIPLE_DSAS",
1457 0x50 => "LDAP_OTHER",
1458 0x51 => "LDAP_SERVER_DOWN",
1459 0x52 => "LDAP_LOCAL_ERROR",
1460 0x53 => "LDAP_ENCODING_ERROR",
1461 0x54 => "LDAP_DECODING_ERROR",
1462 0x55 => "LDAP_TIMEOUT",
1463 0x56 => "LDAP_AUTH_UNKNOWN",
1464 0x57 => "LDAP_FILTER_ERROR",
1465 0x58 => "LDAP_USER_CANCELLED",
1466 0x59 => "LDAP_PARAM_ERROR",
1467 0x5a => "LDAP_NO_MEMORY",
1468 0x5b => "LDAP_CONNECT_ERROR",
1469 0x5c => "LDAP_NOT_SUPPORTED",
1470 0x5d => "LDAP_CONTROL_NOT_FOUND",
1471 0x5e => "LDAP_NO_RESULTS_RETURNED",
1472 0x5f => "LDAP_MORE_RESULTS_TO_RETURN",
1473 0x60 => "LDAP_CLIENT_LOOP",
1474 0x61 => "LDAP_REFERRAL_LIMIT_EXCEEDED",
1475 1000 => "Unknown Net_LDAP2 Error"
1478 return isset($errorMessages[$errorcode]) ?
1479 $errorMessages[$errorcode] :
1480 $errorMessages[NET_LDAP2_ERROR] . ' (' . $errorcode . ')';
1484 * Gets a rootDSE object
1486 * This either fetches a fresh rootDSE object or returns it from
1487 * the internal cache for performance reasons, if possible.
1489 * @param array $attrs Array of attributes to search for
1492 * @return Net_LDAP2_Error|Net_LDAP2_RootDSE Net_LDAP2_Error or Net_LDAP2_RootDSE object
1494 public function &rootDse($attrs = null)
1496 if ($attrs !== null && !is_array($attrs)) {
1497 return PEAR::raiseError('Parameter $attr is expected to be an array!');
1500 $attrs_signature = serialize($attrs);
1502 // see if we need to fetch a fresh object, or if we already
1503 // requested this object with the same attributes
1504 if (true || !array_key_exists($attrs_signature, $this->_rootDSE_cache)) {
1505 $rootdse =& Net_LDAP2_RootDSE::fetch($this, $attrs);
1506 if ($rootdse instanceof Net_LDAP2_Error) {
1510 // search was ok, store rootDSE in cache
1511 $this->_rootDSE_cache[$attrs_signature] = $rootdse;
1513 return $this->_rootDSE_cache[$attrs_signature];
1517 * Alias function of rootDse() for perl-ldap interface
1521 * @return Net_LDAP2_Error|Net_LDAP2_RootDSE
1523 public function &root_dse()
1525 $args = func_get_args();
1526 return call_user_func_array(array(&$this, 'rootDse'), $args);
1530 * Get a schema object
1532 * @param string $dn (optional) Subschema entry dn
1535 * @return Net_LDAP2_Schema|Net_LDAP2_Error Net_LDAP2_Schema or Net_LDAP2_Error object
1537 public function &schema($dn = null)
1539 // Schema caching by Knut-Olav Hoven
1540 // If a schema caching object is registered, we use that to fetch
1542 // See registerSchemaCache() for more info on this.
1543 if ($this->_schema === null) {
1544 if ($this->_schema_cache) {
1545 $cached_schema = $this->_schema_cache->loadSchema();
1546 if ($cached_schema instanceof Net_LDAP2_Error) {
1547 return $cached_schema; // route error to client
1549 if ($cached_schema instanceof Net_LDAP2_Schema) {
1550 $this->_schema = $cached_schema;
1556 // Fetch schema, if not tried before and no cached version available.
1557 // If we are already fetching the schema, we will skip fetching.
1558 if ($this->_schema === null) {
1559 // store a temporary error message so subsequent calls to schema() can
1560 // detect, that we are fetching the schema already.
1561 // Otherwise we will get an infinite loop at Net_LDAP2_Schema::fetch()
1562 $this->_schema = new Net_LDAP2_Error('Schema not initialized');
1563 $this->_schema = Net_LDAP2_Schema::fetch($this, $dn);
1565 // If schema caching is active, advise the cache to store the schema
1566 if ($this->_schema_cache) {
1567 $caching_result = $this->_schema_cache->storeSchema($this->_schema);
1568 if ($caching_result instanceof Net_LDAP2_Error) {
1569 return $caching_result; // route error to client
1573 return $this->_schema;
1577 * Enable/disable persistent schema caching
1579 * Sometimes it might be useful to allow your scripts to cache
1580 * the schema information on disk, so the schema is not fetched
1581 * every time the script runs which could make your scripts run
1584 * This method allows you to register a custom object that
1585 * implements your schema cache. Please see the SchemaCache interface
1586 * (SchemaCache.interface.php) for informations on how to implement this.
1587 * To unregister the cache, pass null as $cache parameter.
1589 * For ease of use, Net_LDAP2 provides a simple file based cache
1590 * which is used in the example below. You may use this, for example,
1591 * to store the schema in a linux tmpfs which results in the schema
1592 * beeing cached inside the RAM which allows nearly instant access.
1594 * // Create the simple file cache object that comes along with Net_LDAP2
1595 * $mySchemaCache_cfg = array(
1596 * 'path' => '/tmp/Net_LDAP2_Schema.cache',
1597 * 'max_age' => 86400 // max age is 24 hours (in seconds)
1599 * $mySchemaCache = new Net_LDAP2_SimpleFileSchemaCache($mySchemaCache_cfg);
1600 * $ldap = new Net_LDAP2::connect(...);
1601 * $ldap->registerSchemaCache($mySchemaCache); // enable caching
1602 * // now each call to $ldap->schema() will get the schema from disk!
1605 * @param Net_LDAP2_SchemaCache|null $cache Object implementing the Net_LDAP2_SchemaCache interface
1607 * @return true|Net_LDAP2_Error
1609 public function registerSchemaCache($cache) {
1611 || (is_object($cache) && in_array('Net_LDAP2_SchemaCache', class_implements($cache))) ) {
1612 $this->_schema_cache = $cache;
1615 return new Net_LDAP2_Error('Custom schema caching object is either no '.
1616 'valid object or does not implement the Net_LDAP2_SchemaCache interface!');
1622 * Checks if phps ldap-extension is loaded
1624 * If it is not loaded, it tries to load it manually using PHPs dl().
1625 * It knows both windows-dll and *nix-so.
1628 * @return Net_LDAP2_Error|true
1630 public static function checkLDAPExtension()
1632 if (!extension_loaded('ldap') && !@dl('ldap.' . PHP_SHLIB_SUFFIX)) {
1633 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.");
1640 * Encodes given attributes from ISO-8859-1 to UTF-8 if needed by schema
1642 * This function takes attributes in an array and then checks against the schema if they need
1643 * UTF8 encoding. If that is so, they will be encoded. An encoded array will be returned and
1644 * can be used for adding or modifying.
1646 * $attributes is expected to be an array with keys describing
1647 * the attribute names and the values as the value of this attribute:
1648 * <code>$attributes = array('cn' => 'foo', 'attr2' => array('mv1', 'mv2'));</code>
1650 * @param array $attributes Array of attributes
1653 * @return array|Net_LDAP2_Error Array of UTF8 encoded attributes or Error
1655 public function utf8Encode($attributes)
1657 return $this->utf8($attributes, 'utf8_encode');
1661 * Decodes the given attribute values from UTF-8 to ISO-8859-1 if needed by schema
1663 * $attributes is expected to be an array with keys describing
1664 * the attribute names and the values as the value of this attribute:
1665 * <code>$attributes = array('cn' => 'foo', 'attr2' => array('mv1', 'mv2'));</code>
1667 * @param array $attributes Array of attributes
1671 * @return array|Net_LDAP2_Error Array with decoded attribute values or Error
1673 public function utf8Decode($attributes)
1675 return $this->utf8($attributes, 'utf8_decode');
1679 * Encodes or decodes UTF-8/ISO-8859-1 attribute values if needed by schema
1681 * @param array $attributes Array of attributes
1682 * @param array $function Function to apply to attribute values
1685 * @return array|Net_LDAP2_Error Array of attributes with function applied to values or Error
1687 protected function utf8($attributes, $function)
1689 if (!is_array($attributes) || array_key_exists(0, $attributes)) {
1690 return PEAR::raiseError('Parameter $attributes is expected to be an associative array');
1693 if (!$this->_schema) {
1694 $this->_schema = $this->schema();
1697 if (!$this->_link || self::isError($this->_schema) || !function_exists($function)) {
1701 if (is_array($attributes) && count($attributes) > 0) {
1703 foreach ($attributes as $k => $v) {
1705 if (!isset($this->_schemaAttrs[$k])) {
1707 $attr = $this->_schema->get('attribute', $k);
1708 if (self::isError($attr)) {
1712 // Encoding is needed if this is a DIR_STR. We assume also
1713 // needed encoding in case the schema contains no syntax
1714 // information (he does not need to, see rfc2252, 4.2)
1715 if (!array_key_exists('syntax', $attr) || false !== strpos($attr['syntax'], '1.3.6.1.4.1.1466.115.121.1.15')) {
1720 $this->_schemaAttrs[$k] = $encode;
1723 $encode = $this->_schemaAttrs[$k];
1728 foreach ($v as $ak => $av) {
1729 $v[$ak] = call_user_func($function, $av);
1732 $v = call_user_func($function, $v);
1735 $attributes[$k] = $v;
1742 * Get the LDAP link resource. It will loop attempting to
1743 * re-establish the connection if the connection attempt fails and
1744 * auto_reconnect has been turned on (see the _config array documentation).
1747 * @return resource LDAP link
1749 public function &getLink()
1751 if ($this->_config['auto_reconnect']) {
1754 // Return the link handle if we are already connected. Otherwise
1755 // try to reconnect.
1757 if ($this->_link !== false) {
1758 return $this->_link;
1760 $this->performReconnect();
1764 return $this->_link;
1769 * Net_LDAP2_Error implements a class for reporting portable LDAP error messages.
1772 * @package Net_LDAP2
1773 * @author Tarjej Huse <tarjei@bergfald.no>
1774 * @license http://www.gnu.org/copyleft/lesser.html LGPL
1775 * @link http://pear.php.net/package/Net_LDAP22/
1777 class Net_LDAP2_Error extends PEAR_Error
1780 * Net_LDAP2_Error constructor.
1782 * @param string $message String with error message.
1783 * @param integer $code Net_LDAP2 error code
1784 * @param integer $mode what "error mode" to operate in
1785 * @param mixed $level what error level to use for $mode & PEAR_ERROR_TRIGGER
1786 * @param mixed $debuginfo additional debug info, such as the last query
1791 public function __construct($message = 'Net_LDAP2_Error', $code = NET_LDAP2_ERROR, $mode = PEAR_ERROR_RETURN,
1792 $level = E_USER_NOTICE, $debuginfo = null)
1794 if (is_int($code)) {
1795 $this->PEAR_Error($message . ': ' . Net_LDAP2::errorMessage($code), $code, $mode, $level, $debuginfo);
1797 $this->PEAR_Error("$message: $code", NET_LDAP2_ERROR, $mode, $level, $debuginfo);