File indexing completed on 2024-05-12 06:02:43

0001 <?php
0002 /**
0003  * Zend Framework
0004  *
0005  * LICENSE
0006  *
0007  * This source file is subject to the new BSD license that is bundled
0008  * with this package in the file LICENSE.txt.
0009  * It is also available through the world-wide-web at this URL:
0010  * http://framework.zend.com/license/new-bsd
0011  * If you did not receive a copy of the license and are unable to
0012  * obtain it through the world-wide-web, please send an email
0013  * to license@zend.com so we can send you a copy immediately.
0014  *
0015  * @category   Zend
0016  * @package    Zend_Ldap
0017  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0018  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0019  * @version    $Id$
0020  */
0021 
0022 /**
0023  * Zend_Ldap_Dn provides an API for DN manipulation
0024  *
0025  * @category   Zend
0026  * @package    Zend_Ldap
0027  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0028  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0029  */
0030 class Zend_Ldap_Dn implements ArrayAccess
0031 {
0032     const ATTR_CASEFOLD_NONE  = 'none';
0033     const ATTR_CASEFOLD_UPPER = 'upper';
0034     const ATTR_CASEFOLD_LOWER = 'lower';
0035 
0036     /**
0037      * The default case fold to use
0038      *
0039      * @var string
0040      */
0041     protected static $_defaultCaseFold = self::ATTR_CASEFOLD_NONE;
0042 
0043     /**
0044      * The case fold used for this instance
0045      *
0046      * @var string
0047      */
0048     protected $_caseFold;
0049 
0050     /**
0051      * The DN data
0052      *
0053      * @var array
0054      */
0055     protected $_dn;
0056 
0057     /**
0058      * Creates a DN from an array or a string
0059      *
0060      * @param  string|array $dn
0061      * @param  string|null  $caseFold
0062      * @return Zend_Ldap_Dn
0063      * @throws Zend_Ldap_Exception
0064      */
0065     public static function factory($dn, $caseFold = null)
0066     {
0067         if (is_array($dn)) {
0068             return self::fromArray($dn, $caseFold);
0069         } else if (is_string($dn)) {
0070             return self::fromString($dn, $caseFold);
0071         } else {
0072             /**
0073              * Zend_Ldap_Exception
0074              */
0075             // require_once 'Zend/Ldap/Exception.php';
0076             throw new Zend_Ldap_Exception(null, 'Invalid argument type for $dn');
0077         }
0078     }
0079 
0080     /**
0081      * Creates a DN from a string
0082      *
0083      * @param  string      $dn
0084      * @param  string|null $caseFold
0085      * @return Zend_Ldap_Dn
0086      * @throws Zend_Ldap_Exception
0087      */
0088     public static function fromString($dn, $caseFold = null)
0089     {
0090         $dn = trim($dn);
0091         if (empty($dn)) {
0092             $dnArray = array();
0093         } else {
0094             $dnArray = self::explodeDn((string)$dn);
0095         }
0096         return new self($dnArray, $caseFold);
0097     }
0098 
0099     /**
0100      * Creates a DN from an array
0101      *
0102      * @param  array       $dn
0103      * @param  string|null $caseFold
0104      * @return Zend_Ldap_Dn
0105      * @throws Zend_Ldap_Exception
0106      */
0107     public static function fromArray(array $dn, $caseFold = null)
0108     {
0109          return new self($dn, $caseFold);
0110     }
0111 
0112     /**
0113      * Constructor
0114      *
0115      * @param array       $dn
0116      * @param string|null $caseFold
0117      */
0118     protected function __construct(array $dn, $caseFold)
0119     {
0120         $this->_dn = $dn;
0121         $this->setCaseFold($caseFold);
0122     }
0123 
0124     /**
0125      * Gets the RDN of the current DN
0126      *
0127      * @param  string $caseFold
0128      * @return array
0129      * @throws Zend_Ldap_Exception if DN has no RDN (empty array)
0130      */
0131     public function getRdn($caseFold = null)
0132     {
0133         $caseFold = self::_sanitizeCaseFold($caseFold, $this->_caseFold);
0134         return self::_caseFoldRdn($this->get(0, 1, $caseFold), null);
0135     }
0136 
0137     /**
0138      * Gets the RDN of the current DN as a string
0139      *
0140      * @param  string $caseFold
0141      * @return string
0142      * @throws Zend_Ldap_Exception if DN has no RDN (empty array)
0143      */
0144     public function getRdnString($caseFold = null)
0145     {
0146         $caseFold = self::_sanitizeCaseFold($caseFold, $this->_caseFold);
0147         return self::implodeRdn($this->getRdn(), $caseFold);
0148     }
0149 
0150     /**
0151      * Get the parent DN $levelUp levels up the tree
0152      *
0153      * @param  int $levelUp
0154      * @return Zend_Ldap_Dn
0155      */
0156     public function getParentDn($levelUp = 1)
0157     {
0158         $levelUp = (int)$levelUp;
0159         if ($levelUp < 1 || $levelUp >= count($this->_dn)) {
0160             /**
0161              * Zend_Ldap_Exception
0162              */
0163             // require_once 'Zend/Ldap/Exception.php';
0164             throw new Zend_Ldap_Exception(null, 'Cannot retrieve parent DN with given $levelUp');
0165         }
0166         $newDn = array_slice($this->_dn, $levelUp);
0167         return new self($newDn, $this->_caseFold);
0168     }
0169 
0170     /**
0171      * Get a DN part
0172      *
0173      * @param  int    $index
0174      * @param  int    $length
0175      * @param  string $caseFold
0176      * @return array
0177      * @throws Zend_Ldap_Exception if index is illegal
0178      */
0179     public function get($index, $length = 1, $caseFold = null)
0180     {
0181         $caseFold = self::_sanitizeCaseFold($caseFold, $this->_caseFold);
0182         $this->_assertIndex($index);
0183         $length = (int)$length;
0184         if ($length <= 0) {
0185             $length = 1;
0186         }
0187         if ($length === 1) {
0188             return self::_caseFoldRdn($this->_dn[$index], $caseFold);
0189         }
0190         else {
0191             return self::_caseFoldDn(array_slice($this->_dn, $index, $length, false), $caseFold);
0192         }
0193     }
0194 
0195     /**
0196      * Set a DN part
0197      *
0198      * @param  int   $index
0199      * @param  array $value
0200      * @return Zend_Ldap_Dn Provides a fluent interface
0201      * @throws Zend_Ldap_Exception if index is illegal
0202      */
0203     public function set($index, array $value)
0204     {
0205         $this->_assertIndex($index);
0206         self::_assertRdn($value);
0207         $this->_dn[$index] = $value;
0208         return $this;
0209     }
0210 
0211     /**
0212      * Remove a DN part
0213      *
0214      * @param  int $index
0215      * @param  int $length
0216      * @return Zend_Ldap_Dn Provides a fluent interface
0217      * @throws Zend_Ldap_Exception if index is illegal
0218      */
0219     public function remove($index, $length = 1)
0220     {
0221         $this->_assertIndex($index);
0222         $length = (int)$length;
0223         if ($length <= 0) {
0224             $length = 1;
0225         }
0226         array_splice($this->_dn, $index, $length, null);
0227         return $this;
0228     }
0229 
0230     /**
0231      * Append a DN part
0232      *
0233      * @param  array $value
0234      * @return Zend_Ldap_Dn Provides a fluent interface
0235      */
0236     public function append(array $value)
0237     {
0238         self::_assertRdn($value);
0239         $this->_dn[] = $value;
0240         return $this;
0241     }
0242 
0243     /**
0244      * Prepend a DN part
0245      *
0246      * @param  array $value
0247      * @return Zend_Ldap_Dn Provides a fluent interface
0248      */
0249     public function prepend(array $value)
0250     {
0251         self::_assertRdn($value);
0252         array_unshift($this->_dn, $value);
0253         return $this;
0254     }
0255 
0256     /**
0257      * Insert a DN part
0258      *
0259      * @param  int   $index
0260      * @param  array $value
0261      * @return Zend_Ldap_Dn Provides a fluent interface
0262      * @throws Zend_Ldap_Exception if index is illegal
0263      */
0264     public function insert($index, array $value)
0265     {
0266         $this->_assertIndex($index);
0267         self::_assertRdn($value);
0268         $first = array_slice($this->_dn, 0, $index + 1);
0269         $second = array_slice($this->_dn, $index + 1);
0270         $this->_dn = array_merge($first, array($value), $second);
0271         return $this;
0272     }
0273 
0274     /**
0275      * Assert index is correct and usable
0276      *
0277      * @param  mixed $index
0278      * @return boolean
0279      * @throws Zend_Ldap_Exception
0280      */
0281     protected function _assertIndex($index)
0282     {
0283         if (!is_int($index)) {
0284             /**
0285              * Zend_Ldap_Exception
0286              */
0287             // require_once 'Zend/Ldap/Exception.php';
0288             throw new Zend_Ldap_Exception(null, 'Parameter $index must be an integer');
0289         }
0290         if ($index < 0 || $index >= count($this->_dn)) {
0291             /**
0292              * Zend_Ldap_Exception
0293              */
0294             // require_once 'Zend/Ldap/Exception.php';
0295             throw new Zend_Ldap_Exception(null, 'Parameter $index out of bounds');
0296         }
0297         return true;
0298     }
0299 
0300     /**
0301      * Assert if value is in a correct RDN format
0302      *
0303      * @param  array $value
0304      * @return boolean
0305      * @throws Zend_Ldap_Exception
0306      */
0307     protected static function _assertRdn(array $value)
0308     {
0309         if (count($value)<1) {
0310             /**
0311              * Zend_Ldap_Exception
0312              */
0313             // require_once 'Zend/Ldap/Exception.php';
0314             throw new Zend_Ldap_Exception(null, 'RDN Array is malformed: it must have at least one item');
0315         }
0316 
0317         foreach (array_keys($value) as $key) {
0318             if (!is_string($key)) {
0319                 /**
0320                  * Zend_Ldap_Exception
0321                  */
0322                 // require_once 'Zend/Ldap/Exception.php';
0323                 throw new Zend_Ldap_Exception(null, 'RDN Array is malformed: it must use string keys');
0324             }
0325         }
0326     }
0327 
0328     /**
0329      * Sets the case fold
0330      *
0331      * @param string|null $caseFold
0332      */
0333     public function setCaseFold($caseFold)
0334     {
0335         $this->_caseFold = self::_sanitizeCaseFold($caseFold, self::$_defaultCaseFold);
0336     }
0337 
0338     /**
0339      * Return DN as a string
0340      *
0341      * @param  string $caseFold
0342      * @return string
0343      * @throws Zend_Ldap_Exception
0344      */
0345     public function toString($caseFold = null)
0346     {
0347         $caseFold = self::_sanitizeCaseFold($caseFold, $this->_caseFold);
0348         return self::implodeDn($this->_dn, $caseFold);
0349     }
0350 
0351     /**
0352      * Return DN as an array
0353      *
0354      * @param  string $caseFold
0355      * @return array
0356      */
0357     public function toArray($caseFold = null)
0358     {
0359         $caseFold = self::_sanitizeCaseFold($caseFold, $this->_caseFold);
0360 
0361         if ($caseFold === self::ATTR_CASEFOLD_NONE) {
0362             return $this->_dn;
0363         } else {
0364             return self::_caseFoldDn($this->_dn, $caseFold);
0365         }
0366     }
0367 
0368     /**
0369      * Do a case folding on a RDN
0370      *
0371      * @param  array  $part
0372      * @param  string $caseFold
0373      * @return array
0374      */
0375     protected static function _caseFoldRdn(array $part, $caseFold)
0376     {
0377         switch ($caseFold) {
0378             case self::ATTR_CASEFOLD_UPPER:
0379                 return array_change_key_case($part, CASE_UPPER);
0380             case self::ATTR_CASEFOLD_LOWER:
0381                 return array_change_key_case($part, CASE_LOWER);
0382             case self::ATTR_CASEFOLD_NONE:
0383             default:
0384                 return $part;
0385         }
0386     }
0387 
0388     /**
0389      * Do a case folding on a DN ort part of it
0390      *
0391      * @param  array  $dn
0392      * @param  string $caseFold
0393      * @return array
0394      */
0395     protected static function _caseFoldDn(array $dn, $caseFold)
0396     {
0397         $return = array();
0398         foreach ($dn as $part) {
0399             $return[] = self::_caseFoldRdn($part, $caseFold);
0400         }
0401         return $return;
0402     }
0403 
0404     /**
0405      * Cast to string representation {@see toString()}
0406      *
0407      * @return string
0408      */
0409     public function __toString()
0410     {
0411         return $this->toString();
0412     }
0413 
0414     /**
0415      * Required by the ArrayAccess implementation
0416      *
0417      * @param  int $offset
0418      * @return boolean
0419      */
0420     public function offsetExists($offset)
0421     {
0422         $offset = (int)$offset;
0423         if ($offset < 0 || $offset >= count($this->_dn)) {
0424             return false;
0425         } else {
0426             return true;
0427         }
0428     }
0429 
0430     /**
0431      * Proxy to {@see get()}
0432      * Required by the ArrayAccess implementation
0433      *
0434      * @param  int $offset
0435      * @return array
0436      */
0437      public function offsetGet($offset)
0438      {
0439          return $this->get($offset, 1, null);
0440      }
0441 
0442      /**
0443       * Proxy to {@see set()}
0444       * Required by the ArrayAccess implementation
0445       *
0446       * @param int   $offset
0447       * @param array $value
0448       */
0449      public function offsetSet($offset, $value)
0450      {
0451          $this->set($offset, $value);
0452      }
0453 
0454      /**
0455       * Proxy to {@see remove()}
0456       * Required by the ArrayAccess implementation
0457       *
0458       * @param int $offset
0459       */
0460      public function offsetUnset($offset)
0461      {
0462          $this->remove($offset, 1);
0463      }
0464 
0465     /**
0466      * Sets the default case fold
0467      *
0468      * @param string $caseFold
0469      */
0470     public static function setDefaultCaseFold($caseFold)
0471     {
0472         self::$_defaultCaseFold = self::_sanitizeCaseFold($caseFold, self::ATTR_CASEFOLD_NONE);
0473     }
0474 
0475     /**
0476      * Sanitizes the case fold
0477      *
0478      * @param  string $caseFold
0479      * @return string
0480      */
0481     protected static function _sanitizeCaseFold($caseFold, $default)
0482     {
0483         switch ($caseFold) {
0484             case self::ATTR_CASEFOLD_NONE:
0485             case self::ATTR_CASEFOLD_UPPER:
0486             case self::ATTR_CASEFOLD_LOWER:
0487                 return $caseFold;
0488                 break;
0489             default:
0490                 return $default;
0491                 break;
0492         }
0493     }
0494 
0495     /**
0496      * Escapes a DN value according to RFC 2253
0497      *
0498      * Escapes the given VALUES according to RFC 2253 so that they can be safely used in LDAP DNs.
0499      * The characters ",", "+", """, "\", "<", ">", ";", "#", " = " with a special meaning in RFC 2252
0500      * are preceeded by ba backslash. Control characters with an ASCII code < 32 are represented as \hexpair.
0501      * Finally all leading and trailing spaces are converted to sequences of \20.
0502      * @see Net_LDAP2_Util::escape_dn_value() from Benedikt Hallinger <beni@php.net>
0503      * @link http://pear.php.net/package/Net_LDAP2
0504      * @author Benedikt Hallinger <beni@php.net>
0505      *
0506      * @param  string|array $values An array containing the DN values that should be escaped
0507      * @return array The array $values, but escaped
0508      */
0509     public static function escapeValue($values = array())
0510     {
0511         /**
0512          * @see Zend_Ldap_Converter
0513          */
0514         // require_once 'Zend/Ldap/Converter.php';
0515 
0516         if (!is_array($values)) $values = array($values);
0517         foreach ($values as $key => $val) {
0518             // Escaping of filter meta characters
0519             $val = str_replace(array('\\', ',', '+', '"', '<', '>', ';', '#', '=', ),
0520                 array('\\\\', '\,', '\+', '\"', '\<', '\>', '\;', '\#', '\='), $val);
0521             $val = Zend_Ldap_Converter::ascToHex32($val);
0522 
0523             // Convert all leading and trailing spaces to sequences of \20.
0524             if (preg_match('/^(\s*)(.+?)(\s*)$/', $val, $matches)) {
0525                 $val = $matches[2];
0526                 for ($i = 0; $i<strlen($matches[1]); $i++) {
0527                     $val = '\20' . $val;
0528                 }
0529                 for ($i = 0; $i<strlen($matches[3]); $i++) {
0530                     $val = $val . '\20';
0531                 }
0532             }
0533             if (null === $val) $val = '\0';  // apply escaped "null" if string is empty
0534             $values[$key] = $val;
0535         }
0536         return (count($values) == 1) ? $values[0] : $values;
0537     }
0538 
0539     /**
0540      * Undoes the conversion done by {@link escapeValue()}.
0541      *
0542      * Any escape sequence starting with a baskslash - hexpair or special character -
0543      * will be transformed back to the corresponding character.
0544      * @see Net_LDAP2_Util::escape_dn_value() from Benedikt Hallinger <beni@php.net>
0545      * @link http://pear.php.net/package/Net_LDAP2
0546      * @author Benedikt Hallinger <beni@php.net>
0547      *
0548      * @param  string|array $values Array of DN Values
0549      * @return array Same as $values, but unescaped
0550      */
0551     public static function unescapeValue($values = array())
0552     {
0553         /**
0554          * @see Zend_Ldap_Converter
0555          */
0556         // require_once 'Zend/Ldap/Converter.php';
0557 
0558         if (!is_array($values)) $values = array($values);
0559         foreach ($values as $key => $val) {
0560             // strip slashes from special chars
0561             $val = str_replace(array('\\\\', '\,', '\+', '\"', '\<', '\>', '\;', '\#', '\='),
0562                 array('\\', ',', '+', '"', '<', '>', ';', '#', '=', ), $val);
0563             $values[$key] = Zend_Ldap_Converter::hex32ToAsc($val);
0564         }
0565         return (count($values) == 1) ? $values[0] : $values;
0566     }
0567 
0568     /**
0569      * Creates an array containing all parts of the given DN.
0570      *
0571      * Array will be of type
0572      * array(
0573      *      array("cn" => "name1", "uid" => "user"),
0574      *      array("cn" => "name2"),
0575      *      array("dc" => "example"),
0576      *      array("dc" => "org")
0577      * )
0578      * for a DN of cn=name1+uid=user,cn=name2,dc=example,dc=org.
0579      *
0580      * @param  string $dn
0581      * @param  array  $keys     An optional array to receive DN keys (e.g. CN, OU, DC, ...)
0582      * @param  array  $vals     An optional array to receive DN values
0583      * @param  string $caseFold
0584      * @return array
0585      * @throws Zend_Ldap_Exception
0586      */
0587     public static function explodeDn($dn, array &$keys = null, array &$vals = null,
0588         $caseFold = self::ATTR_CASEFOLD_NONE)
0589     {
0590         $k = array();
0591         $v = array();
0592         if (!self::checkDn($dn, $k, $v, $caseFold)) {
0593             /**
0594              * Zend_Ldap_Exception
0595              */
0596             // require_once 'Zend/Ldap/Exception.php';
0597             throw new Zend_Ldap_Exception(null, 'DN is malformed');
0598         }
0599         $ret = array();
0600         for ($i = 0; $i < count($k); $i++) {
0601             if (is_array($k[$i]) && is_array($v[$i]) && (count($k[$i]) === count($v[$i]))) {
0602                 $multi = array();
0603                 for ($j = 0; $j < count($k[$i]); $j++) {
0604                     $key=$k[$i][$j];
0605                     $val=$v[$i][$j];
0606                     $multi[$key] = $val;
0607                 }
0608                 $ret[] = $multi;
0609             } else if (is_string($k[$i]) && is_string($v[$i])) {
0610                 $ret[] = array($k[$i] => $v[$i]);
0611             }
0612         }
0613         if ($keys !== null) $keys = $k;
0614         if ($vals !== null) $vals = $v;
0615         return $ret;
0616     }
0617 
0618     /**
0619      * @param  string $dn       The DN to parse
0620      * @param  array  $keys     An optional array to receive DN keys (e.g. CN, OU, DC, ...)
0621      * @param  array  $vals     An optional array to receive DN values
0622      * @param  string $caseFold
0623      * @return boolean True if the DN was successfully parsed or false if the string is not a valid DN.
0624      */
0625     public static function checkDn($dn, array &$keys = null, array &$vals = null,
0626         $caseFold = self::ATTR_CASEFOLD_NONE)
0627     {
0628         /* This is a classic state machine parser. Each iteration of the
0629          * loop processes one character. State 1 collects the key. When equals ( = )
0630          * is encountered the state changes to 2 where the value is collected
0631          * until a comma (,) or semicolon (;) is encountered after which we switch back
0632          * to state 1. If a backslash (\) is encountered, state 3 is used to collect the
0633          * following character without engaging the logic of other states.
0634          */
0635         $key = null;
0636         $value = null;
0637         $slen = strlen($dn);
0638         $state = 1;
0639         $ko = $vo = 0;
0640         $multi = false;
0641         $ka = array();
0642         $va = array();
0643         for ($di = 0; $di <= $slen; $di++) {
0644             $ch = ($di == $slen) ? 0 : $dn[$di];
0645             switch ($state) {
0646                 case 1: // collect key
0647                     if ($ch === '=') {
0648                         $key = trim(substr($dn, $ko, $di - $ko));
0649                         if ($caseFold == self::ATTR_CASEFOLD_LOWER) $key = strtolower($key);
0650                         else if ($caseFold == self::ATTR_CASEFOLD_UPPER) $key = strtoupper($key);
0651                         if (is_array($multi)) {
0652                             $keyId = strtolower($key);
0653                             if (in_array($keyId, $multi)) {
0654                                 return false;
0655                             }
0656                             $ka[count($ka)-1][] = $key;
0657                             $multi[] = $keyId;
0658                         } else {
0659                             $ka[] = $key;
0660                         }
0661                         $state = 2;
0662                         $vo = $di + 1;
0663                     } else if ($ch === ',' || $ch === ';' || $ch === '+') {
0664                         return false;
0665                     }
0666                     break;
0667                 case 2: // collect value
0668                     if ($ch === '\\') {
0669                         $state = 3;
0670                     } else if ($ch === ',' || $ch === ';' || $ch === 0 || $ch === '+') {
0671                         $value = self::unescapeValue(trim(substr($dn, $vo, $di - $vo)));
0672                         if (is_array($multi)) {
0673                             $va[count($va)-1][] = $value;
0674                         } else {
0675                             $va[] = $value;
0676                         }
0677                         $state = 1;
0678                         $ko = $di + 1;
0679                         if ($ch === '+' && $multi === false) {
0680                             $lastKey = array_pop($ka);
0681                             $lastVal = array_pop($va);
0682                             $ka[] = array($lastKey);
0683                             $va[] = array($lastVal);
0684                             $multi = array(strtolower($lastKey));
0685                         } else if ($ch === ','|| $ch === ';' || $ch === 0) {
0686                             $multi = false;
0687                         }
0688                     } else if ($ch === '=') {
0689                         return false;
0690                     }
0691                     break;
0692                 case 3: // escaped
0693                     $state = 2;
0694                     break;
0695             }
0696         }
0697 
0698         if ($keys !== null) {
0699             $keys = $ka;
0700         }
0701         if ($vals !== null) {
0702             $vals = $va;
0703         }
0704 
0705         return ($state === 1 && $ko > 0);
0706     }
0707 
0708     /**
0709      * Returns a DN part in the form $attribute = $value
0710      *
0711      * This method supports the creation of multi-valued RDNs
0712      * $part must contain an even number of elemets.
0713      *
0714      * @param  array  $attribute
0715      * @param  string $caseFold
0716      * @return string
0717      * @throws Zend_Ldap_Exception
0718      */
0719     public static function implodeRdn(array $part, $caseFold = null)
0720     {
0721         self::_assertRdn($part);
0722         $part = self::_caseFoldRdn($part, $caseFold);
0723         $rdnParts = array();
0724         foreach ($part as $key => $value) {
0725             $value = self::escapeValue($value);
0726             $keyId = strtolower($key);
0727             $rdnParts[$keyId] =  implode('=', array($key, $value));
0728         }
0729         ksort($rdnParts, SORT_STRING);
0730         return implode('+', $rdnParts);
0731     }
0732 
0733     /**
0734      * Implodes an array in the form delivered by {@link explodeDn()}
0735      * to a DN string.
0736      *
0737      * $dnArray must be of type
0738      * array(
0739      *      array("cn" => "name1", "uid" => "user"),
0740      *      array("cn" => "name2"),
0741      *      array("dc" => "example"),
0742      *      array("dc" => "org")
0743      * )
0744      *
0745      * @param  array  $dnArray
0746      * @param  string $caseFold
0747      * @param  string $separator
0748      * @return string
0749      * @throws Zend_Ldap_Exception
0750      */
0751     public static function implodeDn(array $dnArray, $caseFold = null, $separator = ',')
0752     {
0753         $parts = array();
0754         foreach ($dnArray as $p) {
0755             $parts[] = self::implodeRdn($p, $caseFold);
0756         }
0757         return implode($separator, $parts);
0758     }
0759 
0760     /**
0761      * Checks if given $childDn is beneath $parentDn subtree.
0762      *
0763      * @param  string|Zend_Ldap_Dn $childDn
0764      * @param  string|Zend_Ldap_Dn $parentDn
0765      * @return boolean
0766      */
0767     public static function isChildOf($childDn, $parentDn)
0768     {
0769         try {
0770             $keys = array();
0771             $vals = array();
0772             if ($childDn instanceof Zend_Ldap_Dn) {
0773                 $cdn = $childDn->toArray(Zend_Ldap_Dn::ATTR_CASEFOLD_LOWER);
0774             } else {
0775                 $cdn = self::explodeDn($childDn, $keys, $vals, Zend_Ldap_Dn::ATTR_CASEFOLD_LOWER);
0776             }
0777             if ($parentDn instanceof Zend_Ldap_Dn) {
0778                 $pdn = $parentDn->toArray(Zend_Ldap_Dn::ATTR_CASEFOLD_LOWER);
0779             } else {
0780                 $pdn = self::explodeDn($parentDn, $keys, $vals, Zend_Ldap_Dn::ATTR_CASEFOLD_LOWER);
0781             }
0782         }
0783         catch (Zend_Ldap_Exception $e) {
0784             return false;
0785         }
0786 
0787         $startIndex = count($cdn)-count($pdn);
0788         if ($startIndex<0) return false;
0789         for ($i = 0; $i<count($pdn); $i++) {
0790             if ($cdn[$i+$startIndex] != $pdn[$i]) return false;
0791         }
0792         return true;
0793     }
0794 }