00001 <?php
00002
00029 class Zend_Ldap
00030 {
00031 const SEARCH_SCOPE_SUB = 1;
00032 const SEARCH_SCOPE_ONE = 2;
00033 const SEARCH_SCOPE_BASE = 3;
00034
00035 const ACCTNAME_FORM_DN = 1;
00036 const ACCTNAME_FORM_USERNAME = 2;
00037 const ACCTNAME_FORM_BACKSLASH = 3;
00038 const ACCTNAME_FORM_PRINCIPAL = 4;
00039
00045 private $_connectString;
00046
00052 protected $_options = null;
00053
00059 protected $_resource = null;
00060
00066 protected $_rootDse = null;
00067
00073 protected $_schema = null;
00074
00080 public static function filterEscape($str)
00081 {
00085 require_once 'Zend/Ldap/Filter/Abstract.php';
00086 return Zend_Ldap_Filter_Abstract::escapeValue($str);
00087 }
00088
00097 public static function explodeDn($dn, array &$keys = null, array &$vals = null)
00098 {
00102 require_once 'Zend/Ldap/Dn.php';
00103 return Zend_Ldap_Dn::checkDn($dn, $keys, $vals);
00104 }
00105
00112 public function __construct($options = array())
00113 {
00114 $this->setOptions($options);
00115 }
00116
00122 public function __destruct()
00123 {
00124 $this->disconnect();
00125 }
00126
00130 public function getResource()
00131 {
00132 return $this->_resource;
00133 }
00134
00140 public function getLastErrorCode()
00141 {
00142 $ret = @ldap_get_option($this->getResource(), LDAP_OPT_ERROR_NUMBER, $err);
00143 if ($ret === true) {
00144 if ($err <= -1 && $err >= -17) {
00148 require_once 'Zend/Ldap/Exception.php';
00149
00150
00151
00152 $err = Zend_Ldap_Exception::LDAP_SERVER_DOWN + (-$err - 1);
00153 }
00154 return $err;
00155 }
00156 return 0;
00157 }
00158
00166 public function getLastError(&$errorCode = null, array &$errorMessages = null)
00167 {
00168 $errorCode = $this->getLastErrorCode();
00169 $errorMessages = array();
00170
00171
00172
00173
00174
00175 $estr1 = @ldap_error($this->getResource());
00176 if ($errorCode !== 0 && $estr1 === 'Success') {
00177 $estr1 = @ldap_err2str($errorCode);
00178 }
00179 if (!empty($estr1)) {
00180 $errorMessages[] = $estr1;
00181 }
00182
00183 @ldap_get_option($this->getResource(), LDAP_OPT_ERROR_STRING, $estr2);
00184 if (!empty($estr2) && !in_array($estr2, $errorMessages)) {
00185 $errorMessages[] = $estr2;
00186 }
00187
00188 $message = '';
00189 if ($errorCode > 0) {
00190 $message = '0x' . dechex($errorCode) . ' ';
00191 } else {
00192 $message = '';
00193 }
00194 if (count($errorMessages) > 0) {
00195 $message .= '(' . implode('; ', $errorMessages) . ')';
00196 } else {
00197 $message .= '(no error message from LDAP)';
00198 }
00199 return $message;
00200 }
00201
00226 public function setOptions($options)
00227 {
00228 if ($options instanceof Zend_Config) {
00229 $options = $options->toArray();
00230 }
00231
00232 $permittedOptions = array(
00233 'host' => null,
00234 'port' => 0,
00235 'useSsl' => false,
00236 'username' => null,
00237 'password' => null,
00238 'bindRequiresDn' => false,
00239 'baseDn' => null,
00240 'accountCanonicalForm' => null,
00241 'accountDomainName' => null,
00242 'accountDomainNameShort' => null,
00243 'accountFilterFormat' => null,
00244 'allowEmptyPassword' => false,
00245 'useStartTls' => false,
00246 'optReferrals' => false,
00247 'tryUsernameSplit' => true,
00248 );
00249
00250 foreach ($permittedOptions as $key => $val) {
00251 if (array_key_exists($key, $options)) {
00252 $val = $options[$key];
00253 unset($options[$key]);
00254
00255
00256
00257 switch ($key) {
00258 case 'port':
00259 case 'accountCanonicalForm':
00260 $permittedOptions[$key] = (int)$val;
00261 break;
00262 case 'useSsl':
00263 case 'bindRequiresDn':
00264 case 'allowEmptyPassword':
00265 case 'useStartTls':
00266 case 'optReferrals':
00267 case 'tryUsernameSplit':
00268 $permittedOptions[$key] = ($val === true ||
00269 $val === '1' || strcasecmp($val, 'true') == 0);
00270 break;
00271 default:
00272 $permittedOptions[$key] = trim($val);
00273 break;
00274 }
00275 }
00276 }
00277 if (count($options) > 0) {
00278 $key = key($options);
00282 require_once 'Zend/Ldap/Exception.php';
00283 throw new Zend_Ldap_Exception(null, "Unknown Zend_Ldap option: $key");
00284 }
00285 $this->_options = $permittedOptions;
00286 return $this;
00287 }
00288
00292 public function getOptions()
00293 {
00294 return $this->_options;
00295 }
00296
00300 protected function _getHost()
00301 {
00302 return $this->_options['host'];
00303 }
00304
00308 protected function _getPort()
00309 {
00310 return $this->_options['port'];
00311 }
00312
00316 protected function _getUseSsl()
00317 {
00318 return $this->_options['useSsl'];
00319 }
00320
00324 protected function _getUsername()
00325 {
00326 return $this->_options['username'];
00327 }
00328
00332 protected function _getPassword()
00333 {
00334 return $this->_options['password'];
00335 }
00336
00340 protected function _getBindRequiresDn()
00341 {
00342 return $this->_options['bindRequiresDn'];
00343 }
00344
00350 public function getBaseDn()
00351 {
00352 return $this->_options['baseDn'];
00353 }
00354
00359 protected function _getAccountCanonicalForm()
00360 {
00361
00362
00363
00364
00365
00366
00367 $accountCanonicalForm = $this->_options['accountCanonicalForm'];
00368 if (!$accountCanonicalForm) {
00369 $accountDomainName = $this->_getAccountDomainName();
00370 $accountDomainNameShort = $this->_getAccountDomainNameShort();
00371 if ($accountDomainNameShort) {
00372 $accountCanonicalForm = Zend_Ldap::ACCTNAME_FORM_BACKSLASH;
00373 } else if ($accountDomainName) {
00374 $accountCanonicalForm = Zend_Ldap::ACCTNAME_FORM_PRINCIPAL;
00375 } else {
00376 $accountCanonicalForm = Zend_Ldap::ACCTNAME_FORM_USERNAME;
00377 }
00378 }
00379
00380 return $accountCanonicalForm;
00381 }
00382
00386 protected function _getAccountDomainName()
00387 {
00388 return $this->_options['accountDomainName'];
00389 }
00390
00394 protected function _getAccountDomainNameShort()
00395 {
00396 return $this->_options['accountDomainNameShort'];
00397 }
00398
00403 protected function _getAccountFilterFormat()
00404 {
00405 return $this->_options['accountFilterFormat'];
00406 }
00407
00411 protected function _getAllowEmptyPassword()
00412 {
00413 return $this->_options['allowEmptyPassword'];
00414 }
00415
00419 protected function _getUseStartTls()
00420 {
00421 return $this->_options['useStartTls'];
00422 }
00423
00427 protected function _getOptReferrals()
00428 {
00429 return $this->_options['optReferrals'];
00430 }
00431
00435 protected function _getTryUsernameSplit()
00436 {
00437 return $this->_options['tryUsernameSplit'];
00438 }
00439
00443 protected function _getAccountFilter($acctname)
00444 {
00448 require_once 'Zend/Ldap/Filter/Abstract.php';
00449 $this->_splitName($acctname, $dname, $aname);
00450 $accountFilterFormat = $this->_getAccountFilterFormat();
00451 $aname = Zend_Ldap_Filter_Abstract::escapeValue($aname);
00452 if ($accountFilterFormat) {
00453 return sprintf($accountFilterFormat, $aname);
00454 }
00455 if (!$this->_getBindRequiresDn()) {
00456
00457 return sprintf("(&(objectClass=user)(sAMAccountName=%s))", $aname);
00458 }
00459 return sprintf("(&(objectClass=posixAccount)(uid=%s))", $aname);
00460 }
00461
00468 protected function _splitName($name, &$dname, &$aname)
00469 {
00470 $dname = null;
00471 $aname = $name;
00472
00473 if (!$this->_getTryUsernameSplit()) {
00474 return;
00475 }
00476
00477 $pos = strpos($name, '@');
00478 if ($pos) {
00479 $dname = substr($name, $pos + 1);
00480 $aname = substr($name, 0, $pos);
00481 } else {
00482 $pos = strpos($name, '\\');
00483 if ($pos) {
00484 $dname = substr($name, 0, $pos);
00485 $aname = substr($name, $pos + 1);
00486 }
00487 }
00488 }
00489
00495 protected function _getAccountDn($acctname)
00496 {
00500 require_once 'Zend/Ldap/Dn.php';
00501 if (Zend_Ldap_Dn::checkDn($acctname)) return $acctname;
00502 $acctname = $this->getCanonicalAccountName($acctname, Zend_Ldap::ACCTNAME_FORM_USERNAME);
00503 $acct = $this->_getAccount($acctname, array('dn'));
00504 return $acct['dn'];
00505 }
00506
00511 protected function _isPossibleAuthority($dname)
00512 {
00513 if ($dname === null) {
00514 return true;
00515 }
00516 $accountDomainName = $this->_getAccountDomainName();
00517 $accountDomainNameShort = $this->_getAccountDomainNameShort();
00518 if ($accountDomainName === null && $accountDomainNameShort === null) {
00519 return true;
00520 }
00521 if (strcasecmp($dname, $accountDomainName) == 0) {
00522 return true;
00523 }
00524 if (strcasecmp($dname, $accountDomainNameShort) == 0) {
00525 return true;
00526 }
00527 return false;
00528 }
00529
00536 public function getCanonicalAccountName($acctname, $form = 0)
00537 {
00538 $this->_splitName($acctname, $dname, $uname);
00539
00540 if (!$this->_isPossibleAuthority($dname)) {
00544 require_once 'Zend/Ldap/Exception.php';
00545 throw new Zend_Ldap_Exception(null,
00546 "Binding domain is not an authority for user: $acctname",
00547 Zend_Ldap_Exception::LDAP_X_DOMAIN_MISMATCH);
00548 }
00549
00550 if (!$uname) {
00554 require_once 'Zend/Ldap/Exception.php';
00555 throw new Zend_Ldap_Exception(null, "Invalid account name syntax: $acctname");
00556 }
00557
00558 if (function_exists('mb_strtolower')) {
00559 $uname = mb_strtolower($uname, 'UTF-8');
00560 } else {
00561 $uname = strtolower($uname);
00562 }
00563
00564 if ($form === 0) {
00565 $form = $this->_getAccountCanonicalForm();
00566 }
00567
00568 switch ($form) {
00569 case Zend_Ldap::ACCTNAME_FORM_DN:
00570 return $this->_getAccountDn($acctname);
00571 case Zend_Ldap::ACCTNAME_FORM_USERNAME:
00572 return $uname;
00573 case Zend_Ldap::ACCTNAME_FORM_BACKSLASH:
00574 $accountDomainNameShort = $this->_getAccountDomainNameShort();
00575 if (!$accountDomainNameShort) {
00579 require_once 'Zend/Ldap/Exception.php';
00580 throw new Zend_Ldap_Exception(null, 'Option required: accountDomainNameShort');
00581 }
00582 return "$accountDomainNameShort\\$uname";
00583 case Zend_Ldap::ACCTNAME_FORM_PRINCIPAL:
00584 $accountDomainName = $this->_getAccountDomainName();
00585 if (!$accountDomainName) {
00589 require_once 'Zend/Ldap/Exception.php';
00590 throw new Zend_Ldap_Exception(null, 'Option required: accountDomainName');
00591 }
00592 return "$uname@$accountDomainName";
00593 default:
00597 require_once 'Zend/Ldap/Exception.php';
00598 throw new Zend_Ldap_Exception(null, "Unknown canonical name form: $form");
00599 }
00600 }
00601
00607 protected function _getAccount($acctname, array $attrs = null)
00608 {
00609 $baseDn = $this->getBaseDn();
00610 if (!$baseDn) {
00614 require_once 'Zend/Ldap/Exception.php';
00615 throw new Zend_Ldap_Exception(null, 'Base DN not set');
00616 }
00617
00618 $accountFilter = $this->_getAccountFilter($acctname);
00619 if (!$accountFilter) {
00623 require_once 'Zend/Ldap/Exception.php';
00624 throw new Zend_Ldap_Exception(null, 'Invalid account filter');
00625 }
00626
00627 if (!is_resource($this->getResource())) {
00628 $this->bind();
00629 }
00630
00631 $accounts = $this->search($accountFilter, $baseDn, self::SEARCH_SCOPE_SUB, $attrs);
00632 $count = $accounts->count();
00633 if ($count === 1) {
00634 $acct = $accounts->getFirst();
00635 $accounts->close();
00636 return $acct;
00637 } else if ($count === 0) {
00641 require_once 'Zend/Ldap/Exception.php';
00642 $code = Zend_Ldap_Exception::LDAP_NO_SUCH_OBJECT;
00643 $str = "No object found for: $accountFilter";
00644 } else {
00648 require_once 'Zend/Ldap/Exception.php';
00649 $code = Zend_Ldap_Exception::LDAP_OPERATIONS_ERROR;
00650 $str = "Unexpected result count ($count) for: $accountFilter";
00651 }
00652 $accounts->close();
00656 require_once 'Zend/Ldap/Exception.php';
00657 throw new Zend_Ldap_Exception($this, $str, $code);
00658 }
00659
00663 public function disconnect()
00664 {
00665 if (is_resource($this->getResource())) {
00666 if (!extension_loaded('ldap')) {
00670 require_once 'Zend/Ldap/Exception.php';
00671 throw new Zend_Ldap_Exception(null, 'LDAP extension not loaded',
00672 Zend_Ldap_Exception::LDAP_X_EXTENSION_NOT_LOADED);
00673 }
00674 @ldap_unbind($this->getResource());
00675 }
00676 $this->_resource = null;
00677 return $this;
00678 }
00679
00688 public function connect($host = null, $port = null, $useSsl = null, $useStartTls = null)
00689 {
00690 if ($host === null) {
00691 $host = $this->_getHost();
00692 }
00693 if ($port === null) {
00694 $port = $this->_getPort();
00695 } else {
00696 $port = (int)$port;
00697 }
00698 if ($useSsl === null) {
00699 $useSsl = $this->_getUseSsl();
00700 } else {
00701 $useSsl = (bool)$useSsl;
00702 }
00703 if ($useStartTls === null) {
00704 $useStartTls = $this->_getUseStartTls();
00705 } else {
00706 $useStartTls = (bool)$useStartTls;
00707 }
00708
00709 if (!$host) {
00713 require_once 'Zend/Ldap/Exception.php';
00714 throw new Zend_Ldap_Exception(null, 'A host parameter is required');
00715 }
00716
00717
00718
00719
00720
00721
00722
00723 $url = ($useSsl) ? "ldaps://$host" : "ldap://$host";
00724 if ($port) {
00725 $url .= ":$port";
00726 }
00727
00728
00729
00730
00731
00732 $this->_connectString = $url;
00733
00734 $this->disconnect();
00735
00736 if (!extension_loaded('ldap')) {
00740 require_once 'Zend/Ldap/Exception.php';
00741 throw new Zend_Ldap_Exception(null, 'LDAP extension not loaded',
00742 Zend_Ldap_Exception::LDAP_X_EXTENSION_NOT_LOADED);
00743 }
00744
00745
00746
00747
00748 $resource = ($useSsl) ? @ldap_connect($url) : @ldap_connect($host, $port);
00749
00750 if (is_resource($resource) === true) {
00751 $this->_resource = $resource;
00752
00753 $optReferrals = ($this->_getOptReferrals()) ? 1 : 0;
00754 if (@ldap_set_option($resource, LDAP_OPT_PROTOCOL_VERSION, 3) &&
00755 @ldap_set_option($resource, LDAP_OPT_REFERRALS, $optReferrals)) {
00756 if ($useSsl || !$useStartTls || @ldap_start_tls($resource)) {
00757 return $this;
00758 }
00759 }
00760
00764 require_once 'Zend/Ldap/Exception.php';
00765 $zle = new Zend_Ldap_Exception($this, "$host:$port");
00766 $this->disconnect();
00767 throw $zle;
00768 }
00772 require_once 'Zend/Ldap/Exception.php';
00773 throw new Zend_Ldap_Exception(null, "Failed to connect to LDAP server: $host:$port");
00774 }
00775
00782 public function bind($username = null, $password = null)
00783 {
00784 $moreCreds = true;
00785
00786 if ($username === null) {
00787 $username = $this->_getUsername();
00788 $password = $this->_getPassword();
00789 $moreCreds = false;
00790 }
00791
00792 if ($username === null) {
00793
00794
00795 $password = null;
00796 } else {
00797
00798
00802 require_once 'Zend/Ldap/Dn.php';
00803 if (!Zend_Ldap_Dn::checkDn($username)) {
00804 if ($this->_getBindRequiresDn()) {
00805
00806
00807
00808 if ($moreCreds) {
00809 try {
00810 $username = $this->_getAccountDn($username);
00811 } catch (Zend_Ldap_Exception $zle) {
00812 switch ($zle->getCode()) {
00813 case Zend_Ldap_Exception::LDAP_NO_SUCH_OBJECT:
00814 case Zend_Ldap_Exception::LDAP_X_DOMAIN_MISMATCH:
00815 case Zend_Ldap_Exception::LDAP_X_EXTENSION_NOT_LOADED:
00816 throw $zle;
00817 }
00818 throw new Zend_Ldap_Exception(null,
00819 'Failed to retrieve DN for account: ' . $username .
00820 ' [' . $zle->getMessage() . ']',
00821 Zend_Ldap_Exception::LDAP_OPERATIONS_ERROR);
00822 }
00823 } else {
00827 require_once 'Zend/Ldap/Exception.php';
00828 throw new Zend_Ldap_Exception(null, 'Binding requires username in DN form');
00829 }
00830 } else {
00831 $username = $this->getCanonicalAccountName($username,
00832 $this->_getAccountCanonicalForm());
00833 }
00834 }
00835 }
00836
00837 if (!is_resource($this->getResource())) {
00838 $this->connect();
00839 }
00840
00841 if ($username !== null && $password === '' && $this->_getAllowEmptyPassword() !== true) {
00845 require_once 'Zend/Ldap/Exception.php';
00846 $zle = new Zend_Ldap_Exception(null,
00847 'Empty password not allowed - see allowEmptyPassword option.');
00848 } else {
00849 if (@ldap_bind($this->getResource(), $username, $password)) {
00850 return $this;
00851 }
00852
00853 $message = ($username === null) ? $this->_connectString : $username;
00857 require_once 'Zend/Ldap/Exception.php';
00858 switch ($this->getLastErrorCode()) {
00859 case Zend_Ldap_Exception::LDAP_SERVER_DOWN:
00860
00861
00862
00863 $message = $this->_connectString;
00864 }
00865
00866 $zle = new Zend_Ldap_Exception($this, $message);
00867 }
00868 $this->disconnect();
00869 throw $zle;
00870 }
00871
00884 public function search($filter, $basedn = null, $scope = self::SEARCH_SCOPE_SUB,
00885 array $attributes = array(), $sort = null, $collectionClass = null)
00886 {
00887 if ($basedn === null) {
00888 $basedn = $this->getBaseDn();
00889 }
00890 else if ($basedn instanceof Zend_Ldap_Dn) {
00891 $basedn = $basedn->toString();
00892 }
00893
00894 if ($filter instanceof Zend_Ldap_Filter_Abstract) {
00895 $filter = $filter->toString();
00896 }
00897
00898 switch ($scope) {
00899 case self::SEARCH_SCOPE_ONE:
00900 $search = @ldap_list($this->getResource(), $basedn, $filter, $attributes);
00901 break;
00902 case self::SEARCH_SCOPE_BASE:
00903 $search = @ldap_read($this->getResource(), $basedn, $filter, $attributes);
00904 break;
00905 case self::SEARCH_SCOPE_SUB:
00906 default:
00907 $search = @ldap_search($this->getResource(), $basedn, $filter, $attributes);
00908 break;
00909 }
00910
00911 if($search === false) {
00915 require_once 'Zend/Ldap/Exception.php';
00916 throw new Zend_Ldap_Exception($this, 'searching: ' . $filter);
00917 }
00918 if (!is_null($sort) && is_string($sort)) {
00919 $isSorted = @ldap_sort($this->getResource(), $search, $sort);
00920 if($search === false) {
00924 require_once 'Zend/Ldap/Exception.php';
00925 throw new Zend_Ldap_Exception($this, 'sorting: ' . $sort);
00926 }
00927 }
00928
00932 require_once 'Zend/Ldap/Collection/Iterator/Default.php';
00933 $iterator = new Zend_Ldap_Collection_Iterator_Default($this, $search);
00934 if ($collectionClass === null) {
00938 require_once 'Zend/Ldap/Collection.php';
00939 return new Zend_Ldap_Collection($iterator);
00940 } else {
00941 $collectionClass = (string)$collectionClass;
00942 if (!class_exists($collectionClass)) {
00946 require_once 'Zend/Ldap/Exception.php';
00947 throw new Zend_Ldap_Exception(null,
00948 "Class '$collectionClass' can not be found");
00949 }
00950 if (!is_subclass_of($collectionClass, 'Zend_Ldap_Collection')) {
00954 require_once 'Zend/Ldap/Exception.php';
00955 throw new Zend_Ldap_Exception(null,
00956 "Class '$collectionClass' must subclass 'Zend_Ldap_Collection'");
00957 }
00958 return new $collectionClass($iterator);
00959 }
00960 }
00961
00971 public function count($filter, $basedn = null, $scope = self::SEARCH_SCOPE_SUB)
00972 {
00973 try {
00974 $result = $this->search($filter, $basedn, $scope, array('dn'), null);
00975 } catch (Zend_Ldap_Exception $e) {
00976 if ($e->getCode() === Zend_Ldap_Exception::LDAP_NO_SUCH_OBJECT) return 0;
00977 else throw $e;
00978 }
00979 return $result->count();
00980 }
00981
00989 public function countChildren($dn)
00990 {
00991 return $this->count('(objectClass=*)', $dn, self::SEARCH_SCOPE_ONE);
00992 }
00993
01001 public function exists($dn)
01002 {
01003 return ($this->count('(objectClass=*)', $dn, self::SEARCH_SCOPE_BASE) == 1);
01004 }
01005
01017 public function searchEntries($filter, $basedn = null, $scope = self::SEARCH_SCOPE_SUB,
01018 array $attributes = array(), $sort = null)
01019 {
01020 $result = $this->search($filter, $basedn, $scope, $attributes, $sort);
01021 return $result->toArray();
01022 }
01023
01033 public function getEntry($dn, array $attributes = array(), $throwOnNotFound = false)
01034 {
01035 try {
01036 $result = $this->search("(objectClass=*)", $dn, self::SEARCH_SCOPE_BASE,
01037 $attributes, null);
01038 return $result->getFirst();
01039 } catch (Zend_Ldap_Exception $e){
01040 if ($throwOnNotFound !== false) throw $e;
01041 }
01042 return null;
01043 }
01044
01052 public static function prepareLdapEntryArray(array &$entry)
01053 {
01054 if (array_key_exists('dn', $entry)) unset($entry['dn']);
01055 foreach ($entry as $key => $value) {
01056 if (is_array($value)) {
01057 foreach ($value as $i => $v) {
01058 if (is_null($v)) unset($value[$i]);
01059 else if (!is_scalar($v)) {
01060 throw new InvalidArgumentException('Only scalar values allowed in LDAP data');
01061 } else {
01062 $v = (string)$v;
01063 if (strlen($v) == 0) {
01064 unset($value[$i]);
01065 } else {
01066 $value[$i] = $v;
01067 }
01068 }
01069 }
01070 $entry[$key] = array_values($value);
01071 } else {
01072 if (is_null($value)) $entry[$key] = array();
01073 else if (!is_scalar($value)) {
01074 throw new InvalidArgumentException('Only scalar values allowed in LDAP data');
01075 } else {
01076 $value = (string)$value;
01077 if (strlen($value) == 0) {
01078 $entry[$key] = array();
01079 } else {
01080 $entry[$key] = array($value);
01081 }
01082 }
01083 }
01084 }
01085 $entry = array_change_key_case($entry, CASE_LOWER);
01086 }
01087
01096 public function add($dn, array $entry)
01097 {
01098 if (!($dn instanceof Zend_Ldap_Dn)) {
01099 $dn = Zend_Ldap_Dn::factory($dn, null);
01100 }
01101 self::prepareLdapEntryArray($entry);
01102 foreach ($entry as $key => $value) {
01103 if (is_array($value) && count($value) === 0) {
01104 unset($entry[$key]);
01105 }
01106 }
01107
01108 $rdnParts = $dn->getRdn(Zend_Ldap_Dn::ATTR_CASEFOLD_LOWER);
01109 foreach ($rdnParts as $key => $value) {
01110 $value = Zend_Ldap_Dn::unescapeValue($value);
01111 if (!array_key_exists($key, $entry) ||
01112 !in_array($value, $entry[$key]) ||
01113 count($entry[$key]) !== 1) {
01114 $entry[$key] = array($value);
01115 }
01116 }
01117 $adAttributes = array('distinguishedname', 'instancetype', 'name', 'objectcategory',
01118 'objectguid', 'usnchanged', 'usncreated', 'whenchanged', 'whencreated');
01119 foreach ($adAttributes as $attr) {
01120 if (array_key_exists($attr, $entry)) {
01121 unset($entry[$attr]);
01122 }
01123 }
01124
01125 $isAdded = @ldap_add($this->getResource(), $dn->toString(), $entry);
01126 if($isAdded === false) {
01130 require_once 'Zend/Ldap/Exception.php';
01131 throw new Zend_Ldap_Exception($this, 'adding: ' . $dn->toString());
01132 }
01133 return $this;
01134 }
01135
01144 public function update($dn, array $entry)
01145 {
01146 if (!($dn instanceof Zend_Ldap_Dn)) {
01147 $dn = Zend_Ldap_Dn::factory($dn, null);
01148 }
01149 self::prepareLdapEntryArray($entry);
01150
01151 $rdnParts = $dn->getRdn(Zend_Ldap_Dn::ATTR_CASEFOLD_LOWER);
01152 $adAttributes = array('distinguishedname', 'instancetype', 'name', 'objectcategory',
01153 'objectguid', 'usnchanged', 'usncreated', 'whenchanged', 'whencreated');
01154 $stripAttributes = array_merge(array_keys($rdnParts), $adAttributes);
01155 foreach ($stripAttributes as $attr) {
01156 if (array_key_exists($attr, $entry)) {
01157 unset($entry[$attr]);
01158 }
01159 }
01160
01161 if (count($entry) > 0) {
01162 $isModified = @ldap_modify($this->getResource(), $dn->toString(), $entry);
01163 if($isModified === false) {
01167 require_once 'Zend/Ldap/Exception.php';
01168 throw new Zend_Ldap_Exception($this, 'updating: ' . $dn->toString());
01169 }
01170 }
01171 return $this;
01172 }
01173
01185 public function save($dn, array $entry)
01186 {
01187 if ($dn instanceof Zend_Ldap_Dn) {
01188 $dn = $dn->toString();
01189 }
01190 if ($this->exists($dn)) $this->update($dn, $entry);
01191 else $this->add($dn, $entry);
01192 return $this;
01193 }
01194
01203 public function delete($dn, $recursively = false)
01204 {
01205 if ($dn instanceof Zend_Ldap_Dn) {
01206 $dn = $dn->toString();
01207 }
01208 if ($recursively === true) {
01209 if ($this->countChildren($dn)>0) {
01210 $children = $this->_getChildrenDns($dn);
01211 foreach ($children as $c) {
01212 $this->delete($c, true);
01213 }
01214 }
01215 }
01216 $isDeleted = @ldap_delete($this->getResource(), $dn);
01217 if($isDeleted === false) {
01221 require_once 'Zend/Ldap/Exception.php';
01222 throw new Zend_Ldap_Exception($this, 'deleting: ' . $dn);
01223 }
01224 return $this;
01225 }
01226
01236 protected function _getChildrenDns($parentDn)
01237 {
01238 if ($parentDn instanceof Zend_Ldap_Dn) {
01239 $parentDn = $parentDn->toString();
01240 }
01241 $children = array();
01242 $search = @ldap_list($this->getResource(), $parentDn, '(objectClass=*)', array('dn'));
01243 for ($entry = @ldap_first_entry($this->getResource(), $search);
01244 $entry !== false;
01245 $entry = @ldap_next_entry($this->getResource(), $entry)) {
01246 $childDn = @ldap_get_dn($this->getResource(), $entry);
01247 if ($childDn === false) {
01251 require_once 'Zend/Ldap/Exception.php';
01252 throw new Zend_Ldap_Exception($this, 'getting dn');
01253 }
01254 $children[] = $childDn;
01255 }
01256 @ldap_free_result($search);
01257 return $children;
01258 }
01259
01270 public function moveToSubtree($from, $to, $recursively = false, $alwaysEmulate = false)
01271 {
01272 if ($from instanceof Zend_Ldap_Dn) {
01273 $orgDnParts = $from->toArray();
01274 } else {
01275 $orgDnParts = Zend_Ldap_Dn::explodeDn($from);
01276 }
01277
01278 if ($to instanceof Zend_Ldap_Dn) {
01279 $newParentDnParts = $to->toArray();
01280 } else {
01281 $newParentDnParts = Zend_Ldap_Dn::explodeDn($to);
01282 }
01283
01284 $newDnParts = array_merge(array(array_shift($orgDnParts)), $newParentDnParts);
01285 $newDn = Zend_Ldap_Dn::fromArray($newDnParts);
01286 return $this->rename($from, $newDn, $recursively, $alwaysEmulate);
01287 }
01288
01301 public function move($from, $to, $recursively = false, $alwaysEmulate = false)
01302 {
01303 return $this->rename($from, $to, $recursively, $alwaysEmulate);
01304 }
01305
01318 public function rename($from, $to, $recursively = false, $alwaysEmulate = false)
01319 {
01320 $emulate = (bool)$alwaysEmulate;
01321 if (!function_exists('ldap_rename')) $emulate = true;
01322 else if ($recursively) $emulate = true;
01323
01324 if ($emulate === false) {
01325 if ($from instanceof Zend_Ldap_Dn) {
01326 $from = $from->toString();
01327 }
01328
01329 if ($to instanceof Zend_Ldap_Dn) {
01330 $newDnParts = $to->toArray();
01331 } else {
01332 $newDnParts = Zend_Ldap_Dn::explodeDn($to);
01333 }
01334
01335 $newRdn = Zend_Ldap_Dn::implodeRdn(array_shift($newDnParts));
01336 $newParent = Zend_Ldap_Dn::implodeDn($newDnParts);
01337 $isOK = @ldap_rename($this->getResource(), $from, $newRdn, $newParent, true);
01338 if($isOK === false) {
01342 require_once 'Zend/Ldap/Exception.php';
01343 throw new Zend_Ldap_Exception($this, 'renaming ' . $from . ' to ' . $to);
01344 }
01345 else if (!$this->exists($to)) $emulate = true;
01346 }
01347 if ($emulate) {
01348 $this->copy($from, $to, $recursively);
01349 $this->delete($from, $recursively);
01350 }
01351 return $this;
01352 }
01353
01363 public function copyToSubtree($from, $to, $recursively = false)
01364 {
01365 if ($from instanceof Zend_Ldap_Dn) {
01366 $orgDnParts = $from->toArray();
01367 } else {
01368 $orgDnParts = Zend_Ldap_Dn::explodeDn($from);
01369 }
01370
01371 if ($to instanceof Zend_Ldap_Dn) {
01372 $newParentDnParts = $to->toArray();
01373 } else {
01374 $newParentDnParts = Zend_Ldap_Dn::explodeDn($to);
01375 }
01376
01377 $newDnParts = array_merge(array(array_shift($orgDnParts)), $newParentDnParts);
01378 $newDn = Zend_Ldap_Dn::fromArray($newDnParts);
01379 return $this->copy($from, $newDn, $recursively);
01380 }
01381
01391 public function copy($from, $to, $recursively = false)
01392 {
01393 $entry = $this->getEntry($from, array(), true);
01394
01395 if ($to instanceof Zend_Ldap_Dn) {
01396 $toDnParts = $to->toArray();
01397 } else {
01398 $toDnParts = Zend_Ldap_Dn::explodeDn($to);
01399 }
01400 $this->add($to, $entry);
01401
01402 if ($recursively === true && $this->countChildren($from)>0) {
01403 $children = $this->_getChildrenDns($from);
01404 foreach ($children as $c) {
01405 $cDnParts = Zend_Ldap_Dn::explodeDn($c);
01406 $newChildParts = array_merge(array(array_shift($cDnParts)), $toDnParts);
01407 $newChild = Zend_Ldap_Dn::implodeDn($newChildParts);
01408 $this->copy($c, $newChild, true);
01409 }
01410 }
01411 return $this;
01412 }
01413
01421 public function getNode($dn)
01422 {
01426 require_once 'Zend/Ldap/Node.php';
01427 return Zend_Ldap_Node::fromLdap($dn, $this);
01428 }
01429
01436 public function getBaseNode()
01437 {
01438 return $this->getNode($this->getBaseDn(), $this);
01439 }
01440
01447 public function getRootDse()
01448 {
01449 if ($this->_rootDse === null) {
01453 require_once 'Zend/Ldap/Node/RootDse.php';
01454 $this->_rootDse = Zend_Ldap_Node_RootDse::create($this);
01455 }
01456 return $this->_rootDse;
01457 }
01458
01465 public function getSchema()
01466 {
01467 if ($this->_schema === null) {
01471 require_once 'Zend/Ldap/Node/Schema.php';
01472 $this->_schema = Zend_Ldap_Node_Schema::create($this);
01473 }
01474 return $this->_schema;
01475 }
01476 }