8 * This source file is subject to the new BSD license that is bundled
9 * with this package in the file LICENSE.txt.
10 * It is also available through the world-wide-web at this URL:
11 * http://framework.zend.com/license/new-bsd
12 * If you did not receive a copy of the license and are unable to
13 * obtain it through the world-wide-web, please send an email
14 * to license@zend.com so we can send you a copy immediately.
18 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
19 * @license http://framework.zend.com/license/new-bsd New BSD License
26 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
27 * @license http://framework.zend.com/license/new-bsd New BSD License
31 const SEARCH_SCOPE_SUB
= 1;
32 const SEARCH_SCOPE_ONE
= 2;
33 const SEARCH_SCOPE_BASE
= 3;
35 const ACCTNAME_FORM_DN
= 1;
36 const ACCTNAME_FORM_USERNAME
= 2;
37 const ACCTNAME_FORM_BACKSLASH
= 3;
38 const ACCTNAME_FORM_PRINCIPAL
= 4;
41 * String used with ldap_connect for error handling purposes.
45 private $_connectString;
48 * The options used in connecting, binding, etc.
52 protected $_options = null;
55 * The raw LDAP extension resource.
59 protected $_resource = null;
62 * FALSE if no user is bound to the LDAP resource
63 * NULL if there has been an anonymous bind
64 * username of the currently bound user
66 * @var boolean|null|string
68 protected $_boundUser = false;
75 protected $_rootDse = null;
82 protected $_schema = null;
85 * @deprecated will be removed, use {@see Zend_Ldap_Filter_Abstract::escapeValue()}
86 * @param string $str The string to escape.
87 * @return string The escaped string
89 public static function filterEscape($str)
92 * @see Zend_Ldap_Filter_Abstract
94 require_once 'Zend/Ldap/Filter/Abstract.php';
95 return Zend_Ldap_Filter_Abstract
::escapeValue($str);
99 * @deprecated will be removed, use {@see Zend_Ldap_Dn::checkDn()}
100 * @param string $dn The DN to parse
101 * @param array $keys An optional array to receive DN keys (e.g. CN, OU, DC, ...)
102 * @param array $vals An optional array to receive DN values
103 * @return boolean True if the DN was successfully parsed or false if the string is
106 public static function explodeDn($dn, array &$keys = null, array &$vals = null)
111 require_once 'Zend/Ldap/Dn.php';
112 return Zend_Ldap_Dn
::checkDn($dn, $keys, $vals);
118 * @param array|Zend_Config $options Options used in connecting, binding, etc.
120 * @throws Zend_Ldap_Exception if ext/ldap is not installed
122 public function __construct($options = array())
124 if (!extension_loaded('ldap')) {
126 * @see Zend_Ldap_Exception
128 require_once 'Zend/Ldap/Exception.php';
129 throw new Zend_Ldap_Exception(null, 'LDAP extension not loaded',
130 Zend_Ldap_Exception
::LDAP_X_EXTENSION_NOT_LOADED
);
132 $this->setOptions($options);
140 public function __destruct()
146 * @return resource The raw LDAP extension resource.
148 public function getResource()
150 if (!is_resource($this->_resource
) ||
$this->_boundUser
=== false) {
153 return $this->_resource
;
157 * Return the LDAP error number of the last LDAP command
161 public function getLastErrorCode()
163 $ret = @ldap_get_option
($this->_resource
, LDAP_OPT_ERROR_NUMBER
, $err);
165 if ($err <= -1 && $err >= -17) {
167 * @see Zend_Ldap_Exception
169 require_once 'Zend/Ldap/Exception.php';
170 /* For some reason draft-ietf-ldapext-ldap-c-api-xx.txt error
171 * codes in OpenLDAP are negative values from -1 to -17.
173 $err = Zend_Ldap_Exception
::LDAP_SERVER_DOWN +
(-$err - 1);
181 * Return the LDAP error message of the last LDAP command
183 * @param int $errorCode
184 * @param array $errorMessages
187 public function getLastError(&$errorCode = null, array &$errorMessages = null)
189 $errorCode = $this->getLastErrorCode();
190 $errorMessages = array();
192 /* The various error retrieval functions can return
193 * different things so we just try to collect what we
194 * can and eliminate dupes.
196 $estr1 = @ldap_error
($this->_resource
);
197 if ($errorCode !== 0 && $estr1 === 'Success') {
198 $estr1 = @ldap_err2str
($errorCode);
200 if (!empty($estr1)) {
201 $errorMessages[] = $estr1;
204 @ldap_get_option
($this->_resource
, LDAP_OPT_ERROR_STRING
, $estr2);
205 if (!empty($estr2) && !in_array($estr2, $errorMessages)) {
206 $errorMessages[] = $estr2;
210 if ($errorCode > 0) {
211 $message = '0x' . dechex($errorCode) . ' ';
215 if (count($errorMessages) > 0) {
216 $message .= '(' . implode('; ', $errorMessages) . ')';
218 $message .= '(no error message from LDAP)';
224 * Get the currently bound user
226 * FALSE if no user is bound to the LDAP resource
227 * NULL if there has been an anonymous bind
228 * username of the currently bound user
230 * @return false|null|string
232 public function getBoundUser()
234 return $this->_boundUser
;
238 * Sets the options used in connecting, binding, etc.
248 * accountCanonicalForm
250 * accountDomainNameShort
251 * accountFilterFormat
257 * @param array|Zend_Config $options Options used in connecting, binding, etc.
258 * @return Zend_Ldap Provides a fluent interface
259 * @throws Zend_Ldap_Exception
261 public function setOptions($options)
263 if ($options instanceof Zend_Config
) {
264 $options = $options->toArray();
267 $permittedOptions = array(
273 'bindRequiresDn' => false,
275 'accountCanonicalForm' => null,
276 'accountDomainName' => null,
277 'accountDomainNameShort' => null,
278 'accountFilterFormat' => null,
279 'allowEmptyPassword' => false,
280 'useStartTls' => false,
281 'optReferrals' => false,
282 'tryUsernameSplit' => true,
285 foreach ($permittedOptions as $key => $val) {
286 if (array_key_exists($key, $options)) {
287 $val = $options[$key];
288 unset($options[$key]);
289 /* Enforce typing. This eliminates issues like Zend_Config_Ini
290 * returning '1' as a string (ZF-3163).
294 case 'accountCanonicalForm':
295 $permittedOptions[$key] = (int)$val;
298 case 'bindRequiresDn':
299 case 'allowEmptyPassword':
302 case 'tryUsernameSplit':
303 $permittedOptions[$key] = ($val === true ||
304 $val === '1' ||
strcasecmp($val, 'true') == 0);
307 $permittedOptions[$key] = trim($val);
312 if (count($options) > 0) {
313 $key = key($options);
315 * @see Zend_Ldap_Exception
317 require_once 'Zend/Ldap/Exception.php';
318 throw new Zend_Ldap_Exception(null, "Unknown Zend_Ldap option: $key");
320 $this->_options
= $permittedOptions;
325 * @return array The current options.
327 public function getOptions()
329 return $this->_options
;
333 * @return string The hostname of the LDAP server being used to authenticate accounts
335 protected function _getHost()
337 return $this->_options
['host'];
341 * @return int The port of the LDAP server or 0 to indicate that no port value is set
343 protected function _getPort()
345 return $this->_options
['port'];
349 * @return boolean The default SSL / TLS encrypted transport control
351 protected function _getUseSsl()
353 return $this->_options
['useSsl'];
357 * @return string The default acctname for binding
359 protected function _getUsername()
361 return $this->_options
['username'];
365 * @return string The default password for binding
367 protected function _getPassword()
369 return $this->_options
['password'];
373 * @return boolean Bind requires DN
375 protected function _getBindRequiresDn()
377 return $this->_options
['bindRequiresDn'];
381 * Gets the base DN under which objects of interest are located
385 public function getBaseDn()
387 return $this->_options
['baseDn'];
391 * @return integer Either ACCTNAME_FORM_BACKSLASH, ACCTNAME_FORM_PRINCIPAL or
392 * ACCTNAME_FORM_USERNAME indicating the form usernames should be canonicalized to.
394 protected function _getAccountCanonicalForm()
396 /* Account names should always be qualified with a domain. In some scenarios
397 * using non-qualified account names can lead to security vulnerabilities. If
398 * no account canonical form is specified, we guess based in what domain
399 * names have been supplied.
402 $accountCanonicalForm = $this->_options
['accountCanonicalForm'];
403 if (!$accountCanonicalForm) {
404 $accountDomainName = $this->_getAccountDomainName();
405 $accountDomainNameShort = $this->_getAccountDomainNameShort();
406 if ($accountDomainNameShort) {
407 $accountCanonicalForm = Zend_Ldap
::ACCTNAME_FORM_BACKSLASH
;
408 } else if ($accountDomainName) {
409 $accountCanonicalForm = Zend_Ldap
::ACCTNAME_FORM_PRINCIPAL
;
411 $accountCanonicalForm = Zend_Ldap
::ACCTNAME_FORM_USERNAME
;
415 return $accountCanonicalForm;
419 * @return string The account domain name
421 protected function _getAccountDomainName()
423 return $this->_options
['accountDomainName'];
427 * @return string The short account domain name
429 protected function _getAccountDomainNameShort()
431 return $this->_options
['accountDomainNameShort'];
435 * @return string A format string for building an LDAP search filter to match
438 protected function _getAccountFilterFormat()
440 return $this->_options
['accountFilterFormat'];
444 * @return boolean Allow empty passwords
446 protected function _getAllowEmptyPassword()
448 return $this->_options
['allowEmptyPassword'];
452 * @return boolean The default SSL / TLS encrypted transport control
454 protected function _getUseStartTls()
456 return $this->_options
['useStartTls'];
460 * @return boolean Opt. Referrals
462 protected function _getOptReferrals()
464 return $this->_options
['optReferrals'];
468 * @return boolean Try splitting the username into username and domain
470 protected function _getTryUsernameSplit()
472 return $this->_options
['tryUsernameSplit'];
476 * @return string The LDAP search filter for matching directory accounts
478 protected function _getAccountFilter($acctname)
481 * @see Zend_Ldap_Filter_Abstract
483 require_once 'Zend/Ldap/Filter/Abstract.php';
484 $this->_splitName($acctname, $dname, $aname);
485 $accountFilterFormat = $this->_getAccountFilterFormat();
486 $aname = Zend_Ldap_Filter_Abstract
::escapeValue($aname);
487 if ($accountFilterFormat) {
488 return sprintf($accountFilterFormat, $aname);
490 if (!$this->_getBindRequiresDn()) {
491 // is there a better way to detect this?
492 return sprintf("(&(objectClass=user)(sAMAccountName=%s))", $aname);
494 return sprintf("(&(objectClass=posixAccount)(uid=%s))", $aname);
498 * @param string $name The name to split
499 * @param string $dname The resulting domain name (this is an out parameter)
500 * @param string $aname The resulting account name (this is an out parameter)
503 protected function _splitName($name, &$dname, &$aname)
508 if (!$this->_getTryUsernameSplit()) {
512 $pos = strpos($name, '@');
514 $dname = substr($name, $pos +
1);
515 $aname = substr($name, 0, $pos);
517 $pos = strpos($name, '\\');
519 $dname = substr($name, 0, $pos);
520 $aname = substr($name, $pos +
1);
526 * @param string $acctname The name of the account
527 * @return string The DN of the specified account
528 * @throws Zend_Ldap_Exception
530 protected function _getAccountDn($acctname)
535 require_once 'Zend/Ldap/Dn.php';
536 if (Zend_Ldap_Dn
::checkDn($acctname)) return $acctname;
537 $acctname = $this->getCanonicalAccountName($acctname, Zend_Ldap
::ACCTNAME_FORM_USERNAME
);
538 $acct = $this->_getAccount($acctname, array('dn'));
543 * @param string $dname The domain name to check
546 protected function _isPossibleAuthority($dname)
548 if ($dname === null) {
551 $accountDomainName = $this->_getAccountDomainName();
552 $accountDomainNameShort = $this->_getAccountDomainNameShort();
553 if ($accountDomainName === null && $accountDomainNameShort === null) {
556 if (strcasecmp($dname, $accountDomainName) == 0) {
559 if (strcasecmp($dname, $accountDomainNameShort) == 0) {
566 * @param string $acctname The name to canonicalize
567 * @param int $type The desired form of canonicalization
568 * @return string The canonicalized name in the desired form
569 * @throws Zend_Ldap_Exception
571 public function getCanonicalAccountName($acctname, $form = 0)
573 $this->_splitName($acctname, $dname, $uname);
575 if (!$this->_isPossibleAuthority($dname)) {
577 * @see Zend_Ldap_Exception
579 require_once 'Zend/Ldap/Exception.php';
580 throw new Zend_Ldap_Exception(null,
581 "Binding domain is not an authority for user: $acctname",
582 Zend_Ldap_Exception
::LDAP_X_DOMAIN_MISMATCH
);
587 * @see Zend_Ldap_Exception
589 require_once 'Zend/Ldap/Exception.php';
590 throw new Zend_Ldap_Exception(null, "Invalid account name syntax: $acctname");
593 if (function_exists('mb_strtolower')) {
594 $uname = mb_strtolower($uname, 'UTF-8');
596 $uname = strtolower($uname);
600 $form = $this->_getAccountCanonicalForm();
604 case Zend_Ldap
::ACCTNAME_FORM_DN
:
605 return $this->_getAccountDn($acctname);
606 case Zend_Ldap
::ACCTNAME_FORM_USERNAME
:
608 case Zend_Ldap
::ACCTNAME_FORM_BACKSLASH
:
609 $accountDomainNameShort = $this->_getAccountDomainNameShort();
610 if (!$accountDomainNameShort) {
612 * @see Zend_Ldap_Exception
614 require_once 'Zend/Ldap/Exception.php';
615 throw new Zend_Ldap_Exception(null, 'Option required: accountDomainNameShort');
617 return "$accountDomainNameShort\\$uname";
618 case Zend_Ldap
::ACCTNAME_FORM_PRINCIPAL
:
619 $accountDomainName = $this->_getAccountDomainName();
620 if (!$accountDomainName) {
622 * @see Zend_Ldap_Exception
624 require_once 'Zend/Ldap/Exception.php';
625 throw new Zend_Ldap_Exception(null, 'Option required: accountDomainName');
627 return "$uname@$accountDomainName";
630 * @see Zend_Ldap_Exception
632 require_once 'Zend/Ldap/Exception.php';
633 throw new Zend_Ldap_Exception(null, "Unknown canonical name form: $form");
638 * @param array $attrs An array of names of desired attributes
639 * @return array An array of the attributes representing the account
640 * @throws Zend_Ldap_Exception
642 protected function _getAccount($acctname, array $attrs = null)
644 $baseDn = $this->getBaseDn();
647 * @see Zend_Ldap_Exception
649 require_once 'Zend/Ldap/Exception.php';
650 throw new Zend_Ldap_Exception(null, 'Base DN not set');
653 $accountFilter = $this->_getAccountFilter($acctname);
654 if (!$accountFilter) {
656 * @see Zend_Ldap_Exception
658 require_once 'Zend/Ldap/Exception.php';
659 throw new Zend_Ldap_Exception(null, 'Invalid account filter');
662 if (!is_resource($this->getResource())) {
666 $accounts = $this->search($accountFilter, $baseDn, self
::SEARCH_SCOPE_SUB
, $attrs);
667 $count = $accounts->count();
669 $acct = $accounts->getFirst();
672 } else if ($count === 0) {
674 * @see Zend_Ldap_Exception
676 require_once 'Zend/Ldap/Exception.php';
677 $code = Zend_Ldap_Exception
::LDAP_NO_SUCH_OBJECT
;
678 $str = "No object found for: $accountFilter";
681 * @see Zend_Ldap_Exception
683 require_once 'Zend/Ldap/Exception.php';
684 $code = Zend_Ldap_Exception
::LDAP_OPERATIONS_ERROR
;
685 $str = "Unexpected result count ($count) for: $accountFilter";
689 * @see Zend_Ldap_Exception
691 require_once 'Zend/Ldap/Exception.php';
692 throw new Zend_Ldap_Exception($this, $str, $code);
696 * @return Zend_Ldap Provides a fluent interface
698 public function disconnect()
700 if (is_resource($this->_resource
)) {
701 @ldap_unbind
($this->_resource
);
703 $this->_resource
= null;
704 $this->_boundUser
= false;
709 * To connect using SSL it seems the client tries to verify the server
710 * certificate by default. One way to disable this behavior is to set
711 * 'TLS_REQCERT never' in OpenLDAP's ldap.conf and restarting Apache. Or,
712 * if you really care about the server's cert you can put a cert on the
715 * @param string $host The hostname of the LDAP server to connect to
716 * @param int $port The port number of the LDAP server to connect to
717 * @param boolean $useSsl Use SSL
718 * @param boolean $useStartTls Use STARTTLS
719 * @return Zend_Ldap Provides a fluent interface
720 * @throws Zend_Ldap_Exception
722 public function connect($host = null, $port = null, $useSsl = null, $useStartTls = null)
724 if ($host === null) {
725 $host = $this->_getHost();
727 if ($port === null) {
728 $port = $this->_getPort();
732 if ($useSsl === null) {
733 $useSsl = $this->_getUseSsl();
735 $useSsl = (bool)$useSsl;
737 if ($useStartTls === null) {
738 $useStartTls = $this->_getUseStartTls();
740 $useStartTls = (bool)$useStartTls;
745 * @see Zend_Ldap_Exception
747 require_once 'Zend/Ldap/Exception.php';
748 throw new Zend_Ldap_Exception(null, 'A host parameter is required');
752 /* Because ldap_connect doesn't really try to connect, any connect error
753 * will actually occur during the ldap_bind call. Therefore, we save the
754 * connect string here for reporting it in error handling in bind().
757 if (preg_match_all('~ldap(?:i|s)?://~', $host, $hosts, PREG_SET_ORDER
) > 0) {
758 $this->_connectString
= $host;
763 $this->_connectString
= 'ldaps://' . $host;
766 $this->_connectString
= 'ldap://' . $host;
769 $this->_connectString
.= ':' . $port;
775 /* Only OpenLDAP 2.2 + supports URLs so if SSL is not requested, just
778 $resource = ($useUri) ? @ldap_connect
($this->_connectString
) : @ldap_connect
($host, $port);
780 if (is_resource($resource) === true) {
781 $this->_resource
= $resource;
782 $this->_boundUser
= false;
784 $optReferrals = ($this->_getOptReferrals()) ?
1 : 0;
785 if (@ldap_set_option
($resource, LDAP_OPT_PROTOCOL_VERSION
, 3) &&
786 @ldap_set_option
($resource, LDAP_OPT_REFERRALS
, $optReferrals)) {
787 if ($useSsl ||
!$useStartTls || @ldap_start_tls
($resource)) {
793 * @see Zend_Ldap_Exception
795 require_once 'Zend/Ldap/Exception.php';
796 $zle = new Zend_Ldap_Exception($this, "$host:$port");
801 * @see Zend_Ldap_Exception
803 require_once 'Zend/Ldap/Exception.php';
804 throw new Zend_Ldap_Exception(null, "Failed to connect to LDAP server: $host:$port");
808 * @param string $username The username for authenticating the bind
809 * @param string $password The password for authenticating the bind
810 * @return Zend_Ldap Provides a fluent interface
811 * @throws Zend_Ldap_Exception
813 public function bind($username = null, $password = null)
817 if ($username === null) {
818 $username = $this->_getUsername();
819 $password = $this->_getPassword();
823 if (empty($username)) {
824 /* Perform anonymous bind
829 /* Check to make sure the username is in DN form.
834 require_once 'Zend/Ldap/Dn.php';
835 if (!Zend_Ldap_Dn
::checkDn($username)) {
836 if ($this->_getBindRequiresDn()) {
837 /* moreCreds stops an infinite loop if _getUsername does not
838 * return a DN and the bind requires it
842 $username = $this->_getAccountDn($username);
843 } catch (Zend_Ldap_Exception
$zle) {
844 switch ($zle->getCode()) {
845 case Zend_Ldap_Exception
::LDAP_NO_SUCH_OBJECT
:
846 case Zend_Ldap_Exception
::LDAP_X_DOMAIN_MISMATCH
:
847 case Zend_Ldap_Exception
::LDAP_X_EXTENSION_NOT_LOADED
:
850 throw new Zend_Ldap_Exception(null,
851 'Failed to retrieve DN for account: ' . $username .
852 ' [' . $zle->getMessage() . ']',
853 Zend_Ldap_Exception
::LDAP_OPERATIONS_ERROR
);
857 * @see Zend_Ldap_Exception
859 require_once 'Zend/Ldap/Exception.php';
860 throw new Zend_Ldap_Exception(null, 'Binding requires username in DN form');
863 $username = $this->getCanonicalAccountName($username,
864 $this->_getAccountCanonicalForm());
869 if (!is_resource($this->_resource
)) {
873 if ($username !== null && $password === '' && $this->_getAllowEmptyPassword() !== true) {
875 * @see Zend_Ldap_Exception
877 require_once 'Zend/Ldap/Exception.php';
878 $zle = new Zend_Ldap_Exception(null,
879 'Empty password not allowed - see allowEmptyPassword option.');
881 if (@ldap_bind
($this->_resource
, $username, $password)) {
882 $this->_boundUser
= $username;
886 $message = ($username === null) ?
$this->_connectString
: $username;
888 * @see Zend_Ldap_Exception
890 require_once 'Zend/Ldap/Exception.php';
891 switch ($this->getLastErrorCode()) {
892 case Zend_Ldap_Exception
::LDAP_SERVER_DOWN
:
893 /* If the error is related to establishing a connection rather than binding,
894 * the connect string is more informative than the username.
896 $message = $this->_connectString
;
899 $zle = new Zend_Ldap_Exception($this, $message);
906 * A global LDAP search routine for finding information.
908 * Options can be either passed as single parameters according to the
909 * method signature or as an array with one or more of the following keys
917 * @param string|Zend_Ldap_Filter_Abstract|array $filter
918 * @param string|Zend_Ldap_Dn|null $basedn
919 * @param integer $scope
920 * @param array $attributes
921 * @param string|null $sort
922 * @param string|null $collectionClass
923 * @return Zend_Ldap_Collection
924 * @throws Zend_Ldap_Exception
926 public function search($filter, $basedn = null, $scope = self
::SEARCH_SCOPE_SUB
,
927 array $attributes = array(), $sort = null, $collectionClass = null)
929 if (is_array($filter)) {
930 $options = array_change_key_case($filter, CASE_LOWER
);
931 foreach ($options as $key => $value) {
940 if (is_array($value)) {
941 $attributes = $value;
944 case 'collectionclass':
945 $collectionClass = $value;
951 if ($basedn === null) {
952 $basedn = $this->getBaseDn();
954 else if ($basedn instanceof Zend_Ldap_Dn
) {
955 $basedn = $basedn->toString();
958 if ($filter instanceof Zend_Ldap_Filter_Abstract
) {
959 $filter = $filter->toString();
963 case self
::SEARCH_SCOPE_ONE
:
964 $search = @ldap_list
($this->getResource(), $basedn, $filter, $attributes);
966 case self
::SEARCH_SCOPE_BASE
:
967 $search = @ldap_read
($this->getResource(), $basedn, $filter, $attributes);
969 case self
::SEARCH_SCOPE_SUB
:
971 $search = @ldap_search
($this->getResource(), $basedn, $filter, $attributes);
975 if($search === false) {
977 * @see Zend_Ldap_Exception
979 require_once 'Zend/Ldap/Exception.php';
980 throw new Zend_Ldap_Exception($this, 'searching: ' . $filter);
982 if (!is_null($sort) && is_string($sort)) {
983 $isSorted = @ldap_sort
($this->getResource(), $search, $sort);
984 if($isSorted === false) {
986 * @see Zend_Ldap_Exception
988 require_once 'Zend/Ldap/Exception.php';
989 throw new Zend_Ldap_Exception($this, 'sorting: ' . $sort);
994 * Zend_Ldap_Collection_Iterator_Default
996 require_once 'Zend/Ldap/Collection/Iterator/Default.php';
997 $iterator = new Zend_Ldap_Collection_Iterator_Default($this, $search);
998 return $this->_createCollection($iterator, $collectionClass);
1002 * Extension point for collection creation
1004 * @param Zend_Ldap_Collection_Iterator_Default $iterator
1005 * @param string|null $collectionClass
1006 * @return Zend_Ldap_Collection
1007 * @throws Zend_Ldap_Exception
1009 protected function _createCollection(Zend_Ldap_Collection_Iterator_Default
$iterator, $collectionClass)
1011 if ($collectionClass === null) {
1013 * Zend_Ldap_Collection
1015 require_once 'Zend/Ldap/Collection.php';
1016 return new Zend_Ldap_Collection($iterator);
1018 $collectionClass = (string)$collectionClass;
1019 if (!class_exists($collectionClass)) {
1021 * @see Zend_Ldap_Exception
1023 require_once 'Zend/Ldap/Exception.php';
1024 throw new Zend_Ldap_Exception(null,
1025 "Class '$collectionClass' can not be found");
1027 if (!is_subclass_of($collectionClass, 'Zend_Ldap_Collection')) {
1029 * @see Zend_Ldap_Exception
1031 require_once 'Zend/Ldap/Exception.php';
1032 throw new Zend_Ldap_Exception(null,
1033 "Class '$collectionClass' must subclass 'Zend_Ldap_Collection'");
1035 return new $collectionClass($iterator);
1040 * Count items found by given filter.
1042 * @param string|Zend_Ldap_Filter_Abstract $filter
1043 * @param string|Zend_Ldap_Dn|null $basedn
1044 * @param integer $scope
1046 * @throws Zend_Ldap_Exception
1048 public function count($filter, $basedn = null, $scope = self
::SEARCH_SCOPE_SUB
)
1051 $result = $this->search($filter, $basedn, $scope, array('dn'), null);
1052 } catch (Zend_Ldap_Exception
$e) {
1053 if ($e->getCode() === Zend_Ldap_Exception
::LDAP_NO_SUCH_OBJECT
) return 0;
1056 return $result->count();
1060 * Count children for a given DN.
1062 * @param string|Zend_Ldap_Dn $dn
1064 * @throws Zend_Ldap_Exception
1066 public function countChildren($dn)
1068 return $this->count('(objectClass=*)', $dn, self
::SEARCH_SCOPE_ONE
);
1072 * Check if a given DN exists.
1074 * @param string|Zend_Ldap_Dn $dn
1076 * @throws Zend_Ldap_Exception
1078 public function exists($dn)
1080 return ($this->count('(objectClass=*)', $dn, self
::SEARCH_SCOPE_BASE
) == 1);
1084 * Search LDAP registry for entries matching filter and optional attributes
1086 * Options can be either passed as single parameters according to the
1087 * method signature or as an array with one or more of the following keys
1095 * @param string|Zend_Ldap_Filter_Abstract|array $filter
1096 * @param string|Zend_Ldap_Dn|null $basedn
1097 * @param integer $scope
1098 * @param array $attributes
1099 * @param string|null $sort
1100 * @param boolean $reverseSort
1102 * @throws Zend_Ldap_Exception
1104 public function searchEntries($filter, $basedn = null, $scope = self
::SEARCH_SCOPE_SUB
,
1105 array $attributes = array(), $sort = null, $reverseSort = false)
1107 if (is_array($filter)) {
1108 $filter = array_change_key_case($filter, CASE_LOWER
);
1109 if (isset($filter['collectionclass'])) {
1110 unset($filter['collectionclass']);
1112 if (isset($filter['reversesort'])) {
1113 $reverseSort = $filter['reversesort'];
1114 unset($filter['reversesort']);
1117 $result = $this->search($filter, $basedn, $scope, $attributes, $sort);
1118 $items = $result->toArray();
1119 if ((bool)$reverseSort === true) {
1120 $items = array_reverse($items, false);
1126 * Get LDAP entry by DN
1128 * @param string|Zend_Ldap_Dn $dn
1129 * @param array $attributes
1130 * @param boolean $throwOnNotFound
1132 * @throws Zend_Ldap_Exception
1134 public function getEntry($dn, array $attributes = array(), $throwOnNotFound = false)
1137 $result = $this->search("(objectClass=*)", $dn, self
::SEARCH_SCOPE_BASE
,
1139 return $result->getFirst();
1140 } catch (Zend_Ldap_Exception
$e){
1141 if ($throwOnNotFound !== false) throw $e;
1147 * Prepares an ldap data entry array for insert/update operation
1149 * @param array $entry
1151 * @throws InvalidArgumentException
1153 public static function prepareLdapEntryArray(array &$entry)
1155 if (array_key_exists('dn', $entry)) unset($entry['dn']);
1156 foreach ($entry as $key => $value) {
1157 if (is_array($value)) {
1158 foreach ($value as $i => $v) {
1159 if (is_null($v)) unset($value[$i]);
1160 else if (!is_scalar($v)) {
1161 throw new InvalidArgumentException('Only scalar values allowed in LDAP data');
1164 if (strlen($v) == 0) {
1171 $entry[$key] = array_values($value);
1173 if (is_null($value)) $entry[$key] = array();
1174 else if (!is_scalar($value)) {
1175 throw new InvalidArgumentException('Only scalar values allowed in LDAP data');
1177 $value = (string)$value;
1178 if (strlen($value) == 0) {
1179 $entry[$key] = array();
1181 $entry[$key] = array($value);
1186 $entry = array_change_key_case($entry, CASE_LOWER
);
1190 * Add new information to the LDAP repository
1192 * @param string|Zend_Ldap_Dn $dn
1193 * @param array $entry
1194 * @return Zend_Ldap Provides a fluid interface
1195 * @throws Zend_Ldap_Exception
1197 public function add($dn, array $entry)
1199 if (!($dn instanceof Zend_Ldap_Dn
)) {
1200 $dn = Zend_Ldap_Dn
::factory($dn, null);
1202 self
::prepareLdapEntryArray($entry);
1203 foreach ($entry as $key => $value) {
1204 if (is_array($value) && count($value) === 0) {
1205 unset($entry[$key]);
1209 $rdnParts = $dn->getRdn(Zend_Ldap_Dn
::ATTR_CASEFOLD_LOWER
);
1210 foreach ($rdnParts as $key => $value) {
1211 $value = Zend_Ldap_Dn
::unescapeValue($value);
1212 if (!array_key_exists($key, $entry)) {
1213 $entry[$key] = array($value);
1214 } else if (!in_array($value, $entry[$key])) {
1215 $entry[$key] = array_merge(array($value), $entry[$key]);
1218 $adAttributes = array('distinguishedname', 'instancetype', 'name', 'objectcategory',
1219 'objectguid', 'usnchanged', 'usncreated', 'whenchanged', 'whencreated');
1220 foreach ($adAttributes as $attr) {
1221 if (array_key_exists($attr, $entry)) {
1222 unset($entry[$attr]);
1226 $isAdded = @ldap_add
($this->getResource(), $dn->toString(), $entry);
1227 if($isAdded === false) {
1229 * @see Zend_Ldap_Exception
1231 require_once 'Zend/Ldap/Exception.php';
1232 throw new Zend_Ldap_Exception($this, 'adding: ' . $dn->toString());
1238 * Update LDAP registry
1240 * @param string|Zend_Ldap_Dn $dn
1241 * @param array $entry
1242 * @return Zend_Ldap Provides a fluid interface
1243 * @throws Zend_Ldap_Exception
1245 public function update($dn, array $entry)
1247 if (!($dn instanceof Zend_Ldap_Dn
)) {
1248 $dn = Zend_Ldap_Dn
::factory($dn, null);
1250 self
::prepareLdapEntryArray($entry);
1252 $rdnParts = $dn->getRdn(Zend_Ldap_Dn
::ATTR_CASEFOLD_LOWER
);
1253 foreach ($rdnParts as $key => $value) {
1254 $value = Zend_Ldap_Dn
::unescapeValue($value);
1255 if (array_key_exists($key, $entry) && !in_array($value, $entry[$key])) {
1256 $entry[$key] = array_merge(array($value), $entry[$key]);
1260 $adAttributes = array('distinguishedname', 'instancetype', 'name', 'objectcategory',
1261 'objectguid', 'usnchanged', 'usncreated', 'whenchanged', 'whencreated');
1262 foreach ($adAttributes as $attr) {
1263 if (array_key_exists($attr, $entry)) {
1264 unset($entry[$attr]);
1268 if (count($entry) > 0) {
1269 $isModified = @ldap_modify
($this->getResource(), $dn->toString(), $entry);
1270 if($isModified === false) {
1272 * @see Zend_Ldap_Exception
1274 require_once 'Zend/Ldap/Exception.php';
1275 throw new Zend_Ldap_Exception($this, 'updating: ' . $dn->toString());
1282 * Save entry to LDAP registry.
1284 * Internally decides if entry will be updated to added by calling
1287 * @param string|Zend_Ldap_Dn $dn
1288 * @param array $entry
1289 * @return Zend_Ldap Provides a fluid interface
1290 * @throws Zend_Ldap_Exception
1292 public function save($dn, array $entry)
1294 if ($dn instanceof Zend_Ldap_Dn
) {
1295 $dn = $dn->toString();
1297 if ($this->exists($dn)) $this->update($dn, $entry);
1298 else $this->add($dn, $entry);
1303 * Delete an LDAP entry
1305 * @param string|Zend_Ldap_Dn $dn
1306 * @param boolean $recursively
1307 * @return Zend_Ldap Provides a fluid interface
1308 * @throws Zend_Ldap_Exception
1310 public function delete($dn, $recursively = false)
1312 if ($dn instanceof Zend_Ldap_Dn
) {
1313 $dn = $dn->toString();
1315 if ($recursively === true) {
1316 if ($this->countChildren($dn)>0) {
1317 $children = $this->_getChildrenDns($dn);
1318 foreach ($children as $c) {
1319 $this->delete($c, true);
1323 $isDeleted = @ldap_delete
($this->getResource(), $dn);
1324 if($isDeleted === false) {
1326 * @see Zend_Ldap_Exception
1328 require_once 'Zend/Ldap/Exception.php';
1329 throw new Zend_Ldap_Exception($this, 'deleting: ' . $dn);
1335 * Retrieve the immediate children DNs of the given $parentDn
1337 * This method is used in recursive methods like {@see delete()}
1340 * @param string|Zend_Ldap_Dn $parentDn
1341 * @return array of DNs
1343 protected function _getChildrenDns($parentDn)
1345 if ($parentDn instanceof Zend_Ldap_Dn
) {
1346 $parentDn = $parentDn->toString();
1348 $children = array();
1349 $search = @ldap_list
($this->getResource(), $parentDn, '(objectClass=*)', array('dn'));
1350 for ($entry = @ldap_first_entry
($this->getResource(), $search);
1352 $entry = @ldap_next_entry
($this->getResource(), $entry)) {
1353 $childDn = @ldap_get_dn
($this->getResource(), $entry);
1354 if ($childDn === false) {
1356 * @see Zend_Ldap_Exception
1358 require_once 'Zend/Ldap/Exception.php';
1359 throw new Zend_Ldap_Exception($this, 'getting dn');
1361 $children[] = $childDn;
1363 @ldap_free_result
($search);
1368 * Moves a LDAP entry from one DN to another subtree.
1370 * @param string|Zend_Ldap_Dn $from
1371 * @param string|Zend_Ldap_Dn $to
1372 * @param boolean $recursively
1373 * @param boolean $alwaysEmulate
1374 * @return Zend_Ldap Provides a fluid interface
1375 * @throws Zend_Ldap_Exception
1377 public function moveToSubtree($from, $to, $recursively = false, $alwaysEmulate = false)
1379 if ($from instanceof Zend_Ldap_Dn
) {
1380 $orgDnParts = $from->toArray();
1382 $orgDnParts = Zend_Ldap_Dn
::explodeDn($from);
1385 if ($to instanceof Zend_Ldap_Dn
) {
1386 $newParentDnParts = $to->toArray();
1388 $newParentDnParts = Zend_Ldap_Dn
::explodeDn($to);
1391 $newDnParts = array_merge(array(array_shift($orgDnParts)), $newParentDnParts);
1392 $newDn = Zend_Ldap_Dn
::fromArray($newDnParts);
1393 return $this->rename($from, $newDn, $recursively, $alwaysEmulate);
1397 * Moves a LDAP entry from one DN to another DN.
1399 * This is an alias for {@link rename()}
1401 * @param string|Zend_Ldap_Dn $from
1402 * @param string|Zend_Ldap_Dn $to
1403 * @param boolean $recursively
1404 * @param boolean $alwaysEmulate
1405 * @return Zend_Ldap Provides a fluid interface
1406 * @throws Zend_Ldap_Exception
1408 public function move($from, $to, $recursively = false, $alwaysEmulate = false)
1410 return $this->rename($from, $to, $recursively, $alwaysEmulate);
1414 * Renames a LDAP entry from one DN to another DN.
1416 * This method implicitely moves the entry to another location within the tree.
1418 * @param string|Zend_Ldap_Dn $from
1419 * @param string|Zend_Ldap_Dn $to
1420 * @param boolean $recursively
1421 * @param boolean $alwaysEmulate
1422 * @return Zend_Ldap Provides a fluid interface
1423 * @throws Zend_Ldap_Exception
1425 public function rename($from, $to, $recursively = false, $alwaysEmulate = false)
1427 $emulate = (bool)$alwaysEmulate;
1428 if (!function_exists('ldap_rename')) $emulate = true;
1429 else if ($recursively) $emulate = true;
1431 if ($emulate === false) {
1432 if ($from instanceof Zend_Ldap_Dn
) {
1433 $from = $from->toString();
1436 if ($to instanceof Zend_Ldap_Dn
) {
1437 $newDnParts = $to->toArray();
1439 $newDnParts = Zend_Ldap_Dn
::explodeDn($to);
1442 $newRdn = Zend_Ldap_Dn
::implodeRdn(array_shift($newDnParts));
1443 $newParent = Zend_Ldap_Dn
::implodeDn($newDnParts);
1444 $isOK = @ldap_rename
($this->getResource(), $from, $newRdn, $newParent, true);
1445 if($isOK === false) {
1447 * @see Zend_Ldap_Exception
1449 require_once 'Zend/Ldap/Exception.php';
1450 throw new Zend_Ldap_Exception($this, 'renaming ' . $from . ' to ' . $to);
1452 else if (!$this->exists($to)) $emulate = true;
1455 $this->copy($from, $to, $recursively);
1456 $this->delete($from, $recursively);
1462 * Copies a LDAP entry from one DN to another subtree.
1464 * @param string|Zend_Ldap_Dn $from
1465 * @param string|Zend_Ldap_Dn $to
1466 * @param boolean $recursively
1467 * @return Zend_Ldap Provides a fluid interface
1468 * @throws Zend_Ldap_Exception
1470 public function copyToSubtree($from, $to, $recursively = false)
1472 if ($from instanceof Zend_Ldap_Dn
) {
1473 $orgDnParts = $from->toArray();
1475 $orgDnParts = Zend_Ldap_Dn
::explodeDn($from);
1478 if ($to instanceof Zend_Ldap_Dn
) {
1479 $newParentDnParts = $to->toArray();
1481 $newParentDnParts = Zend_Ldap_Dn
::explodeDn($to);
1484 $newDnParts = array_merge(array(array_shift($orgDnParts)), $newParentDnParts);
1485 $newDn = Zend_Ldap_Dn
::fromArray($newDnParts);
1486 return $this->copy($from, $newDn, $recursively);
1490 * Copies a LDAP entry from one DN to another DN.
1492 * @param string|Zend_Ldap_Dn $from
1493 * @param string|Zend_Ldap_Dn $to
1494 * @param boolean $recursively
1495 * @return Zend_Ldap Provides a fluid interface
1496 * @throws Zend_Ldap_Exception
1498 public function copy($from, $to, $recursively = false)
1500 $entry = $this->getEntry($from, array(), true);
1502 if ($to instanceof Zend_Ldap_Dn
) {
1503 $toDnParts = $to->toArray();
1505 $toDnParts = Zend_Ldap_Dn
::explodeDn($to);
1507 $this->add($to, $entry);
1509 if ($recursively === true && $this->countChildren($from)>0) {
1510 $children = $this->_getChildrenDns($from);
1511 foreach ($children as $c) {
1512 $cDnParts = Zend_Ldap_Dn
::explodeDn($c);
1513 $newChildParts = array_merge(array(array_shift($cDnParts)), $toDnParts);
1514 $newChild = Zend_Ldap_Dn
::implodeDn($newChildParts);
1515 $this->copy($c, $newChild, true);
1522 * Returns the specified DN as a Zend_Ldap_Node
1524 * @param string|Zend_Ldap_Dn $dn
1525 * @return Zend_Ldap_Node|null
1526 * @throws Zend_Ldap_Exception
1528 public function getNode($dn)
1533 require_once 'Zend/Ldap/Node.php';
1534 return Zend_Ldap_Node
::fromLdap($dn, $this);
1538 * Returns the base node as a Zend_Ldap_Node
1540 * @return Zend_Ldap_Node
1541 * @throws Zend_Ldap_Exception
1543 public function getBaseNode()
1545 return $this->getNode($this->getBaseDn(), $this);
1549 * Returns the RootDSE
1551 * @return Zend_Ldap_Node_RootDse
1552 * @throws Zend_Ldap_Exception
1554 public function getRootDse()
1556 if ($this->_rootDse
=== null) {
1558 * @see Zend_Ldap_Node_Schema
1560 require_once 'Zend/Ldap/Node/RootDse.php';
1561 $this->_rootDse
= Zend_Ldap_Node_RootDse
::create($this);
1563 return $this->_rootDse
;
1567 * Returns the schema
1569 * @return Zend_Ldap_Node_Schema
1570 * @throws Zend_Ldap_Exception
1572 public function getSchema()
1574 if ($this->_schema
=== null) {
1576 * @see Zend_Ldap_Node_Schema
1578 require_once 'Zend/Ldap/Node/Schema.php';
1579 $this->_schema
= Zend_Ldap_Node_Schema
::create($this);
1581 return $this->_schema
;