public static function canonicalDN($dn, array $options = array())
{
if ($dn === '') {
// Empty DN is valid.
return $dn;
}
// Options check.
$options = array_merge(array('casefold' => 'upper', 'reverse' => false, 'separator' => ','), $options);
if (!is_array($dn)) {
// It is not clear to me if the perl implementation splits by the
// user defined separator or if it just uses this separator to
// construct the new DN.
$dn = preg_split('/(?<!\\\\)' . preg_quote($options['separator']) . '/', $dn);
// Clear wrong splitting (possibly we have split too much).
$dn = self::_correctDNSplitting($dn, $options['separator']);
} else {
// Is array, check if the array is indexed or associative.
$assoc = false;
foreach ($dn as $dn_key => $dn_part) {
if (!is_int($dn_key)) {
$assoc = true;
break;
}
}
// Convert to indexed, if associative array detected.
if ($assoc) {
$newdn = array();
foreach ($dn as $dn_key => $dn_part) {
if (is_array($dn_part)) {
// We assume here that the RDN parts are also
// associative.
ksort($dn_part, SORT_STRING);
// Copy array as-is, so we can resolve it later.
$newdn[] = $dn_part;
} else {
$newdn[] = $dn_key . '=' . $dn_part;
}
}
$dn =& $newdn;
}
}
// Escaping and casefolding.
foreach ($dn as $pos => $dnval) {
if (is_array($dnval)) {
// Subarray detected, this means most probably that we had a
// multivalued DN part, which must be resolved.
$dnval_new = '';
foreach ($dnval as $subkey => $subval) {
// Build RDN part.
if (!is_int($subkey)) {
$subval = $subkey . '=' . $subval;
}
$subval_processed = self::canonicalDN($subval, $options);
if (false === $subval_processed) {
return false;
}
$dnval_new .= $subval_processed . '+';
}
// Store RDN part, strip last plus.
$dn[$pos] = substr($dnval_new, 0, -1);
} else {
// Try to split multivalued RDNs into array.
$rdns = self::splitRDNMultivalue($dnval);
if (count($rdns) > 1) {
// Multivalued RDN was detected. The RDN value is expected
// to be correctly split by splitRDNMultivalue(). It's time
// to sort the RDN and build the DN.
$rdn_string = '';
// Sort RDN keys alphabetically.
sort($rdns, SORT_STRING);
foreach ($rdns as $rdn) {
$subval_processed = self::canonicalDN($rdn, $options);
if (false === $subval_processed) {
return false;
}
$rdn_string .= $subval_processed . '+';
}
// Store RDN part, strip last plus.
$dn[$pos] = substr($rdn_string, 0, -1);
} else {
// No multivalued RDN. Split at first unescaped "=".
$dn_comp = self::splitAttributeString($rdns[0]);
if (count($dn_comp) != 2) {
throw new Horde_Ldap_Exception('Invalid RDN: ' . $rdns[0]);
}
// Trim left whitespaces because of "cn=foo, l=bar" syntax
// (whitespace after comma).
$ocl = ltrim($dn_comp[0]);
$val = $dn_comp[1];
// Strip 'OID.', otherwise apply casefolding and escaping.
if (substr(Horde_String::lower($ocl), 0, 4) == 'oid.') {
$ocl = substr($ocl, 4);
} else {
if ($options['casefold'] == 'upper') {
$ocl = Horde_String::upper($ocl);
}
if ($options['casefold'] == 'lower') {
$ocl = Horde_String::lower($ocl);
}
$ocl = self::escapeDNValue(array($ocl));
$ocl = $ocl[0];
}
// Escaping of DN value.
// TODO: if the value is already correctly escaped, we get
// double escaping.
$val = self::escapeDNValue(array($val));
$val = str_replace('/', '\\/', $val[0]);
$dn[$pos] = $ocl . '=' . $val;
}
}
}
if ($options['reverse']) {
$dn = array_reverse($dn);
}
return implode($options['separator'], $dn);
}