noleaf = $_SESSION[APPCONFIG]->getValue('appearance', 'disable_default_leaf'); } } public function __clone() { if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) { debug_log('Entered (%%)', 5, 0, __FILE__, __LINE__, __METHOD__, $fargs); } # We need to clone our attributes, when passing back a template with getTemplate foreach ($this->attributes as $key => $value) { $this->attributes[$key] = clone $value; } } /** * Main processing to store the template. * * @param xmldata Parsed xmldata from xml2array object */ protected function storeTemplate($xmldata) { if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) { debug_log('Entered (%%)', 5, 0, __FILE__, __LINE__, __METHOD__, $fargs); } $server = $this->getServer(); $objectclasses = array(); foreach ($xmldata['template'] as $xml_key => $xml_value) { if (DEBUG_ENABLED) { debug_log('Foreach loop Key [%s] Value [%s]', 4, 0, __FILE__, __LINE__, __METHOD__, $xml_key, is_array($xml_value)); } switch ($xml_key) { # Build our object Classes from the DN and Template. case ('objectclasses'): if (DEBUG_ENABLED) { debug_log('Case [%s]', 4, 0, __FILE__, __LINE__, __METHOD__, $xml_key); } if (isset($xmldata['template'][$xml_key]['objectclass'])) { if (is_array($xmldata['template'][$xml_key]['objectclass'])) { foreach ($xmldata['template'][$xml_key]['objectclass'] as $index => $details) { # XML files with only 1 objectClass dont have a numeric index. $soc = $server->getSchemaObjectClass(strtolower($details)); # If we havent recorded this objectclass already, do so now. if (is_object($soc) && ! in_array($soc->getName(), $objectclasses)) { $objectclasses[] = $soc->getName(FALSE); } elseif ( ! is_object($soc) && ! $_SESSION[APPCONFIG]->getValue('appearance', 'hide_template_warning')) { system_message(array( 'title' => _('Automatically removed objectClass from template'), 'body' => sprintf('%s: %s %s', $this->getTitle(), $details, _('removed from template as it is not defined in the schema')), 'type' => 'warn')); } } } else { # XML files with only 1 objectClass dont have a numeric index. $soc = $server->getSchemaObjectClass(strtolower($xmldata['template'][$xml_key]['objectclass'])); # If we havent recorded this objectclass already, do so now. if (is_object($soc) && ! in_array($soc->getName(), $objectclasses)) { $objectclasses[] = $soc->getName(FALSE); } } } break; # Build our attribute list from the DN and Template. case ('attributes'): if (DEBUG_ENABLED) { debug_log('Case [%s]', 4, 0, __FILE__, __LINE__, __METHOD__, $xml_key); } if (is_array($xmldata['template'][$xml_key])) { foreach ($xmldata['template'][$xml_key] as $tattrs) { foreach ($tattrs as $index => $details) { if (DEBUG_ENABLED) { debug_log('Foreach tattrs Key [%s] Value [%s]', 4, 0, __FILE__, __LINE__, __METHOD__, $index, $details); } # If there is no schema definition for the attribute, it will be ignored. if ($sattr = $server->getSchemaAttribute($index)) { if (is_null($this->getAttribute($sattr->getName()))) { $this->addAttribute($sattr->getName(), $details, 'XML'); } } } } masort($this->attributes,'order'); } break; default: if (DEBUG_ENABLED) { debug_log('Case [%s]', 4, 0, __FILE__, __LINE__, __METHOD__, $xml_key); } # Some key definitions need to be an array, some must not be: $allowed_arrays = array('rdn'); $storelower = array('rdn'); $storearray = array('rdn'); # Items that must be stored lowercase if (in_array($xml_key,$storelower)) { if (is_array($xml_value)) { foreach ($xml_value as $index => $value) { $xml_value[$index] = strtolower($value); } } else { $xml_value = strtolower($xml_value); } } # Items that must be stored as arrays if (in_array($xml_key,$storearray) && ! is_array($xml_value)) { $xml_value = array($xml_value); } # Items that should not be an array if (! in_array($xml_key,$allowed_arrays) && is_array($xml_value)) { debug_dump(array(__METHOD__,'key'=>$xml_key,'value'=>$xml_value)); error(sprintf(_('In the XML file (%s), [%s] is an array, it must be a string.'), $this->filename,$xml_key),'error'); } $this->$xml_key = $xml_value; if ($xml_key == 'invalid' && $xml_value) { $this->setInvalid(_('Disabled by XML configuration'), TRUE); } } } if (! count($objectclasses)) { $this->setInvalid(_('ObjectClasses in XML dont exist in LDAP server.')); return; } else { $attribute = $this->addAttribute('objectClass',array('values'=>$objectclasses),'XML'); $attribute->justModified(); $attribute->setRequired(); $attribute->hide(); } $this->rebuildTemplateAttrs(); # Check we have some manditory items. foreach (array('rdn','structural_oclass','visible') as $key) { if (! isset($this->$key) || (! is_array($this->$key) && ! trim($this->$key))) { $this->setInvalid(sprintf(_('Missing %s in the XML file.'),$key)); break; } } # Mark our RDN attributes as RDN $counter = 1; foreach ($this->rdn as $key) { if ((is_null($attribute = $this->getAttribute($key))) && (in_array_ignore_case('extensibleobject',$this->getObjectClasses()))) { $attribute = $this->addAttribute($key,array('values'=>array())); $attribute->show(); } if (! is_null($attribute)) { $attribute->setRDN($counter++); } elseif ($this->isType('creation')) { $this->setInvalid(sprintf(_('Missing RDN attribute %s in the XML file.'), $key)); } } } /** * Is default templates enabled? * This will disable the default template from the engine. * * @return boolean */ protected function hasDefaultTemplate() { if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) { debug_log('Entered (%%)', 5, 0, __FILE__, __LINE__, __METHOD__, $fargs); } if ($_SESSION[APPCONFIG]->getValue('appearance','disable_default_template')) { return FALSE; } else { return TRUE; } } /** * Return the templates of type (creation/modification) * * @param $type * @return array - Array of templates of that type */ protected function readTemplates($type) { if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) { debug_log('Entered (%%)', 5, 0, __FILE__, __LINE__, __METHOD__, $fargs); } $template_xml = new Templates($this->server_id); return $template_xml->getTemplates($type); } /** * This function will perform the following intialisation steps: * + If a DN is set, query the ldap and load the object * + Read our $_REQUEST variable and set the values * After this action, the template should self describe as to whether it is an update, create * or delete. * (OLD values are IGNORED, we will have got them when we build this object from the LDAP server DN.) * @param bool $makeVisible * @param bool $nocache */ public function accept($makeVisible=false,$nocache=false) { if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) { debug_log('Entered (%%)', 5, 0, __FILE__, __LINE__, __METHOD__, $fargs); } $server = $this->getServer(); # If a DN is set, then query the LDAP server for the details. if ($this->dn) { if (! $server->dnExists($this->dn)) { system_message(array( 'title' => __METHOD__, 'body' => sprintf('DN (%s) didnt exist in LDAP?', $this->dn), 'type' => 'info')); } $rdnarray = rdn_explode(strtolower(get_rdn(dn_escape($this->dn)))); $counter = 1; foreach ($server->getDNAttrValues($this->dn,null,LDAP_DEREF_NEVER,array_merge(array('*'),$server->getValue('server','custom_attrs')),$nocache) as $attr => $values) { # We ignore DNs. if ($attr == 'dn') { continue; } $attribute = $this->getAttribute($attr); if (is_null($attribute)) { $attribute = $this->addAttribute($attr, array('values' => $values)); } else if ($attribute->getValues()) { # Override values to those that are defined in the XML file. if ($attribute->getSource() != 'XML') { $attribute->setValue(array_values($values)); } else { $attribute->setOldValue(array_values($values)); } } else { $attribute->initValue(array_values($values)); } # Work out the RDN attributes foreach ($attribute->getValues() as $index => $value) { if (in_array(sprintf('%s=%s', $attribute->getName(), strtolower($attribute->getValue($index))), $rdnarray)) { $attribute->setRDN($counter++); } } if ($makeVisible) { $attribute->show(); } } # Get the Internal Attributes foreach ($server->getDNAttrValues($this->dn,null,LDAP_DEREF_NEVER,array_merge(array('+'),$server->getValue('server','custom_sys_attrs'))) as $attr => $values) { $attribute = $this->getAttribute($attr); if (is_null($attribute)) { $attribute = $this->addAttribute($attr, array('values' => $values)); } else if ($attribute->getValues()) { $attribute->setValue(array_values($values)); } else { $attribute->initValue(array_values($values)); } if (! in_array_ignore_case($attribute->getName(),$server->getValue('server','custom_attrs'))) { $attribute->setInternal(); } } # If this is the default template, and our $_REQUEST has defined our objectclass, then query the schema to get the attributes } elseif ($this->container) { if ($this->isType('default') && ! count($this->getAttributes(true)) && isset($_REQUEST['new_values']['objectclass'])) { $attribute = $this->addAttribute('objectclass',array('values'=>$_REQUEST['new_values']['objectclass'])); $attribute->justModified(); $this->rebuildTemplateAttrs(); unset($_REQUEST['new_values']['objectclass']); } } elseif (get_request('create_base')) { if (get_request('rdn')) { $rdn = explode('=',get_request('rdn')); $attribute = $this->addAttribute($rdn[0],array('values'=>array($rdn[1]))); $attribute->setRDN(1); } } else { debug_dump_backtrace('No DN or CONTAINER?',1); } # Read in our new values. foreach (array('new_values') as $key) { if (isset($_REQUEST[$key])) { foreach ($_REQUEST[$key] as $attr => $values) { # If it isnt an array, silently ignore it. if ( ! is_array($values)) { continue; } # If _REQUEST['skip_array'] with this attr set, we'll ignore this new_value if (isset($_REQUEST['skip_array'][$attr]) && $_REQUEST['skip_array'][$attr] == 'on') { continue; } # Prune out entries with a blank value. foreach ($values as $index => $value) { if ( ! strlen(trim($value))) { unset($values[$index]); } } $attribute = $this->getAttribute($attr); # If the attribute is null, then no attribute exists, silently ignore it (unless this is the default template) if (is_null($attribute) && ( ! $this->isType('default') && ! $this->isType(NULL))) { continue; } # If it is a binary attribute, the post should have base64 encoded the value, we'll need to reverse that if ($server->isAttrBinary($attr)) { foreach ($values as $index => $value) { $values[$index] = base64_decode($value); } } if (is_null($attribute)) { $attribute = $this->addAttribute($attr, array('values' => $values)); if (count($values)) { $attribute->justModified(); } } else { $attribute->setValue(array_values($values)); } } } # Read in our new binary values if (isset($_FILES[$key]['name'])) { foreach ($_FILES[$key]['name'] as $attr => $values) { $new_values = array(); foreach ($values as $index => $details) { # Ignore empty files if ( ! $_FILES[$key]['size'][$attr][$index]) { continue; } if ( ! is_uploaded_file($_FILES[$key]['tmp_name'][$attr][$index])) { if (isset($_FILES[$key]['error'][$attr][$index])) { switch ($_FILES[$key]['error'][$attr][$index]) { # No error; possible file attack! case 0: $msg = _('Security error: The file being uploaded may be malicious.'); break; # Uploaded file exceeds the upload_max_filesize directive in php.ini case 1: $msg = _('The file you uploaded is too large. Please check php.ini, upload_max_size setting'); break; # Uploaded file exceeds the MAX_FILE_SIZE directive specified in the html form case 2: $msg = _('The file you uploaded is too large. Please check php.ini, upload_max_size setting'); break; # Uploaded file was only partially uploaded case 3: $msg = _('The file you selected was only partially uploaded, likley due to a network error.'); break; # No file was uploaded case 4: $msg = _('You left the attribute value blank. Please go back and try again.'); break; # A default error, just in case! :) default: $msg = _('Security error: The file being uploaded may be malicious.'); break; } } else { $msg = _('Security error: The file being uploaded may be malicious.'); } system_message(array( 'title' => _('Upload Binary Attribute Error'), 'body' => $msg, 'type' => 'warn')); } else { $binaryfile = array(); $binaryfile['name'] = $_FILES[$key]['tmp_name'][$attr][$index]; $binaryfile['handle'] = fopen($binaryfile['name'], 'r'); $binaryfile['data'] = fread($binaryfile['handle'], filesize($binaryfile['name'])); fclose($binaryfile['handle']); $new_values[$index] = $binaryfile['data']; } } if (count($new_values)) { $attribute = $this->getAttribute($attr); if (is_null($attribute)) { $attribute = $this->addAttribute($attr, array('values' => $new_values)); } else { foreach ($new_values as $value) { $attribute->addValue($value); } } $attribute->justModified(); } } } } # If there are any single item additions (from the add_attr form for example) if (isset($_REQUEST['single_item_attr'])) { if (isset($_REQUEST['single_item_value'])) { if (! is_array($_REQUEST['single_item_value'])) { $values = array($_REQUEST['single_item_value']); } else { $values = $_REQUEST['single_item_value']; } } elseif (isset($_REQUEST['binary'])) { /* Special case for binary attributes (like jpegPhoto and userCertificate): * we must go read the data from the file and override $_REQUEST['single_item_value'] with the * binary data. Secondly, we must check if the ";binary" option has to be appended to the name * of the attribute. */ if ($_FILES['single_item_value']['size'] === 0) { system_message(array( 'title' => _('Upload Binary Attribute Error'), 'body' => sprintf('%s %s', _('The file you chose is either empty or does not exist.'), _('Please go back and try again.')), 'type' => 'warn')); } else { if (! is_uploaded_file($_FILES['single_item_value']['tmp_name'])) { if (isset($_FILES['single_item_value']['error'])) { switch ($_FILES['single_item_value']['error']) { # No error; possible file attack! case 0: $msg = _('Security error: The file being uploaded may be malicious.'); break; # Uploaded file exceeds the upload_max_filesize directive in php.ini case 1: $msg = _('The file you uploaded is too large. Please check php.ini, upload_max_size setting'); break; # Uploaded file exceeds the MAX_FILE_SIZE directive specified in the html form case 2: $msg = _('The file you uploaded is too large. Please check php.ini, upload_max_size setting'); break; # Uploaded file was only partially uploaded case 3: $msg = _('The file you selected was only partially uploaded, likley due to a network error.'); break; # No file was uploaded case 4: $msg = _('You left the attribute value blank. Please go back and try again.'); break; # A default error, just in case! :) default: $msg = _('Security error: The file being uploaded may be malicious.'); break; } } else { $msg = _('Security error: The file being uploaded may be malicious.'); } system_message(array( 'title'=>_('Upload Binary Attribute Error'),'body'=>$msg,'type'=>'warn'),'index.php'); } $binaryfile = array(); $binaryfile['name'] = $_FILES['single_item_value']['tmp_name']; $binaryfile['handle'] = fopen($binaryfile['name'],'r'); $binaryfile['data'] = fread($binaryfile['handle'],filesize($binaryfile['name'])); fclose($binaryfile['handle']); $values = array($binaryfile['data']); } } if (count($values)) { $attribute = $this->getAttribute($_REQUEST['single_item_attr']); if (is_null($attribute)) { $attribute = $this->addAttribute($_REQUEST['single_item_attr'], array('values' => $values)); } else { $attribute->setValue(array_values($values)); } $attribute->justModified(); } } # If this is the default creation template, we need to set some additional values if ($this->isType('default') && $this->getContext() == 'create') { # Load our schema, based on the objectclasses that may have already been defined. if (! get_request('create_base')) { $this->rebuildTemplateAttrs(); } # Set the RDN attribute $counter = 1; foreach (get_request('rdn_attribute','REQUEST',false,array()) as $key => $value) { $attribute = $this->getAttribute($value); if (! is_null($attribute)) { $attribute->setRDN($counter++); } else { system_message(array( 'title'=>_('No RDN attribute'), 'body'=>_('No RDN attribute was selected'), 'type'=>'warn'),'index.php'); die(); } } } } /** * Set the DN for this template, if we are editing entries * * @param dn The DN of the entry */ public function setDN($dn) { if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) { debug_log('Entered (%%)', 5, 0, __FILE__, __LINE__, __METHOD__, $fargs); } if (isset($this->container)) { system_message(array( 'title' => __METHOD__, 'body' => 'CONTAINER set while setting DN', 'type' => 'info')); } $this->dn = $dn; } /** * Set the RDN attributes * Given an RDN, mark the attributes as RDN attributes. If there is no defined attribute, * then the remaining RDNs will be returned. * * @param RDN * @return array attributes not processed */ public function setRDNAttributes($rdn) { if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) { debug_log('Entered (%%)', 5, 0, __FILE__, __LINE__, __METHOD__, $fargs); } # Setup to work out our RDN. $rdnarray = rdn_explode($rdn); $counter = 1; foreach ($this->getAttributes(true) as $attribute) { foreach ($rdnarray as $index => $rdnattr) { [$attr, $value] = explode('=', $rdnattr); if (strtolower($attr) == $attribute->getName()) { $attribute->setRDN($counter++); unset($rdnarray[$index]); } } } return $rdnarray; } /** * Display the DN for this template entry. If the DN is not set (creating a new entry), then * a generated DN will be produced, taken from the RDN and the CONTAINER details. * * @return dn */ public function getDN() { if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) { debug_log('Entered (%%)', 5, 0, __FILE__, __LINE__, __METHOD__, $fargs, $this->dn); } if ($this->dn) { return $this->dn; } # If DN is not set, our DN will be made from our RDN and Container. elseif ($this->getRDN() && $this->getContainer()) { return sprintf('%s,%s', $this->getRDN(), $this->getContainer()); } # If container is not set, we're probably creating the base elseif ($this->getRDN() && get_request('create_base')) { return $this->getRDN(); } } public function getDNEncode($url=true) { // @todo Be nice to do all this in 1 location if ($url) { return urlencode(preg_replace('/%([0-9a-fA-F]+)/', "%25\\1", $this->getDN())); } else { return preg_replace('/%([0-9a-fA-F]+)/', "%25\\1", $this->getDN()); } } /** * Set the container for this template, if we are creating entries * * @param dn The DN of the container * @todo Trigger a query to the LDAP server and generate an error if the container doesnt exist */ public function setContainer($container) { if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) { debug_log('Entered (%%)', 5, 0, __FILE__, __LINE__, __METHOD__, $fargs); } if (isset($this->dn)) { system_message(array( 'title' => __METHOD__, 'body' => 'DN set while setting CONTAINER', 'type' => 'info')); } $this->container = $container; } /** * Get the DN of the container for this entry * * @return dn DN of the container */ public function getContainer() { if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) { debug_log('Entered (%%)', 5, 1, __FILE__, __LINE__, __METHOD__, $fargs, $this->container); } return $this->container; } public function getContainerEncode($url=true) { // @todo Be nice to do all this in 1 location if ($url) { return urlencode(preg_replace('/%([0-9a-fA-F]+)/', "%25\\1", $this->container)); } else { return preg_replace('/%([0-9a-fA-F]+)/', "%25\\1", $this->container); } } /** * Copy a DN * @param $template * @param $rdn * @param bool $asnew */ public function copy($template,$rdn,$asnew=false) { if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) { debug_log('Entered (%%)', 5, 0, __FILE__, __LINE__, __METHOD__, $fargs); } $rdnarray = rdn_explode($rdn); $counter = 1; foreach ($template->getAttributes(true) as $sattribute) { $attribute = $this->addAttribute($sattribute->getName(false),array('values'=>$sattribute->getValues())); # Set our new RDN, and its values if (is_null($attribute)) { debug_dump_backtrace('Attribute is null, it probably doesnt exist in the destination server?'); } else { # Mark our internal attributes. if ($sattribute->isInternal()) { $attribute->setInternal(); } $modified = false; foreach ($rdnarray as $index => $rdnattr) { [$attr,$value] = explode('=',$rdnattr); if (strtolower($attr) == $attribute->getName()) { # If this is already marked as an RDN, then this multivalue RDN was updated on a previous loop if (! $modified) { $attribute->setValue(array($value)); $attribute->setRDN($counter++); $modified = true; } else { $attribute->addValue($value); } # This attribute has been taken care of, we'll drop it from our list. unset($rdnarray[$index]); } } } // @todo If this is a Jpeg Attribute, we need to mark it read only, since it cant be deleted like text attributes can if (strcasecmp(get_class($attribute),'jpegAttribute') == 0) { $attribute->setReadOnly(); } } # If we have any RDN values left over, there werent in the original entry and need to be added. foreach ($rdnarray as $rdnattr) { [$attr,$value] = explode('=',$rdnattr); $attribute = $this->addAttribute($attr,array('values'=>array($value))); if (is_null($attribute)) { debug_dump_backtrace('Attribute is null, it probably doesnt exist in the destination server?'); } else { $attribute->setRDN($counter++); } } # If we are copying into a new entry, we need to discard all the "old values" if ($asnew) { foreach ($this->getAttributes(TRUE) as $sattribute) { $sattribute->setOldValue(array()); } } } /** * Get Attributes by LDAP type * This function will return a list of attributes by LDAP type (MUST,MAY). * * @param $type * @return array Array of attributes. */ public function getAttrbyLdapType($type) { if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) { debug_log('Entered (%%)', 5, 0, __FILE__, __LINE__, __METHOD__, $fargs); } $result = array(); foreach ($this->attributes as $index => $attribute) { if ($attribute->getLDAPtype() == strtolower($type)) { $result[] = $attribute->getName(); } } return $result; } /** * Return true if this is a MUST,MAY attribute * @param $attr * @param $type * @return bool */ public function isAttrType($attr, $type) { if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) { debug_log('Entered (%%)', 5, 0, __FILE__, __LINE__, __METHOD__, $fargs); } if (in_array(strtolower($attr),$this->getAttrbyLdapType($type))) { return TRUE; } else { return FALSE; } } /** * Return the attributes that comprise the RDN. * * @return array Array of RDN objects */ private function getRDNObjects() { if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) { debug_log('Entered (%%)', 5, 0, __FILE__, __LINE__, __METHOD__, $fargs); } $return = array(); foreach ($this->attributes as $attribute) { if ($attribute->isRDN()) { $return[] = $attribute; } } masort($return,'rdn'); return $return; } /** * Get all the RDNs for this template, in RDN order. * * @return array RDNs in order. */ public function getRDNAttrs() { if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) { debug_log('Entered (%%)', 5, 0, __FILE__, __LINE__, __METHOD__, $fargs); } $return = array(); foreach ($this->getRDNObjects() as $attribute) { # We'll test if two RDN's have the same number (we cant test anywhere else) if (isset($return[$attribute->isRDN()]) && $this->getType() == 'creation') { system_message(array( 'title' => _('RDN attribute sequence already defined'), 'body' => sprintf('%s %s', sprintf(_('There is a problem in template [%s].'), $this->getName()), sprintf(_('RDN attribute sequence [%s] is already used by attribute [%s] and cant be used by attribute [%s] also.'), $attribute->isRDN(), $return[$attribute->isRDN()], $attribute->getName())), 'type' => 'error'), 'index.php'); } $return[$attribute->isRDN()] = $attribute->getName(); } return $return; } /** * Return the RDN for this template. If the DN is already defined, then the RDN will be calculated from it. * If the DN is not set, then the RDN will be calcuated from the template attribute definitions * * @return rdn RDN for this template */ public function getRDN() { if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) { debug_log('Entered (%%)', 5, 0, __FILE__, __LINE__, __METHOD__, $fargs); } # If the DN is set, then the RDN will be calculated from it. if ($this->dn) { return get_rdn($this->dn); } $rdn = ''; foreach ($this->getRDNObjects() as $attribute) { $vals = $attribute->getValues(); # If an RDN attribute has no values, return with an empty string. The calling script should handle this. if (! count($vals)) { return ''; } foreach ($vals as $val) { $rdn .= sprintf('%s=%s+', $attribute->getName(FALSE), $val); } } # Chop the last plus sign off when returning return preg_replace('/\+$/','',$rdn); } /** * Return the attribute name part of the RDN */ public function getRDNAttributeName() { if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) { debug_log('Entered (%%)', 5, 0, __FILE__, __LINE__, __METHOD__, $fargs); } $attr = array(); if ($this->getDN()) { $i = strpos($this->getDN(),','); if ($i !== false) { $attrs = explode('\+',substr($this->getDN(),0,$i)); foreach ($attrs as $id => $attr) { [$name,$value] = explode('=',$attr); $attrs[$id] = $name; } $attr = array_unique($attrs); } } return $attr; } /** * Determine the type of template this is */ public function getContext() { if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) { debug_log('Entered (%%)', 5, 0, __FILE__, __LINE__, __METHOD__, $fargs); } if ($this->getContainer() && get_request('cmd','REQUEST') == 'copy') { return 'copyasnew'; } elseif ($this->getContainer() || get_request('create_base')) { return 'create'; } elseif ($this->getDN()) { return 'edit'; } else { return 'unknown'; } } /** * Test if the template is visible * * @return boolean */ public function isVisible() { if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) { debug_log('Entered (%%)', 5, 1, __FILE__, __LINE__, __METHOD__, $fargs, $this->visible); } return $this->visible; } public function setVisible() { $this->visible = true; } public function setInvisible() { $this->visible = false; } public function getRegExp() { if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) { debug_log('Entered (%%)', 5, 1, __FILE__, __LINE__, __METHOD__, $fargs, $this->regexp); } return $this->regexp; } /** * Test if this template has been marked as a read-only template */ public function isReadOnly() { if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) { debug_log('Entered (%%)', 5, 0, __FILE__, __LINE__, __METHOD__, $fargs); } if ((($this->getContext() == 'edit') && $this->readonly) || $this->getServer()->isReadOnly()) { return TRUE; } else { return FALSE; } } /** * Get the attribute entries * * @param boolean Include the optional attributes * @return array Array of attributes */ public function getAttributes($optional=false) { if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) { debug_log('Entered (%%)', 5, 0, __FILE__, __LINE__, __METHOD__, $fargs); } if ($optional) { return $this->attributes; } $result = array(); foreach ($this->attributes as $attribute) { if (! $attribute->isRequired()) { continue; } $result[] = $attribute; } return $result; } /** * Return a list of attributes that should be shown */ public function getAttributesShown() { if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) { debug_log('Entered (%%)', 5, 0, __FILE__, __LINE__, __METHOD__, $fargs); } $result = array(); foreach ($this->attributes as $attribute) { if ($attribute->isVisible()) { $result[] = $attribute; } } return $result; } /** * Return a list of the internal attributes */ public function getAttributesInternal() { if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) { debug_log('Entered (%%)', 5, 0, __FILE__, __LINE__, __METHOD__, $fargs); } $result = array(); foreach ($this->attributes as $attribute) { if ($attribute->isInternal()) { $result[] = $attribute; } } return $result; } /** * Return the objectclasses defined in this template * * @return array Array of Objects */ public function getObjectClasses() { if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) { debug_log('Entered (%%)', 5, 0, __FILE__, __LINE__, __METHOD__, $fargs); } $attribute = $this->getAttribute('objectclass'); if ($attribute) { return $attribute->getValues(); } else { return array(); } } /** * Get template icon */ public function getIcon() { if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) { debug_log('Entered (%%)', 5, 1, __FILE__, __LINE__, __METHOD__, $fargs, $this->icon); } return isset($this->icon) ? sprintf('%s/%s',IMGDIR,$this->icon) : ''; } /** * Return the template description * * @return string Description */ public function getDescription() { if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) { debug_log('Entered (%%)', 5, 1, __FILE__, __LINE__, __METHOD__, $fargs, $this->description); } return $this->description; } /** * Set a template as invalid * * @param string Message indicating the reason the template has been invalidated * @param bool $admin */ public function setInvalid($msg,$admin=false) { if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) { debug_log('Entered (%%)', 5, 0, __FILE__, __LINE__, __METHOD__, $fargs); } $this->invalid = true; $this->invalid_reason = $msg; $this->invalid_admin = $admin; } /** * Get the template validity or the reason it is invalid * * @return string Invalid reason, or false if not invalid */ public function isInValid() { if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) { debug_log('Entered (%%)', 5, 0, __FILE__, __LINE__, __METHOD__, $fargs); } if ($this->invalid) { return $this->invalid_reason; } else { return FALSE; } } public function isAdminDisabled() { if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) { debug_log('Entered (%%)', 5, 1, __FILE__, __LINE__, __METHOD__, $fargs, $this->invalid_admin); } return $this->invalid_admin; } /** * Set the minimum number of values for an attribute * * @param object Attribute * @param int */ private function setMinValueCount($attr,$value) { if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) { debug_log('Entered (%%)', 5, 0, __FILE__, __LINE__, __METHOD__, $fargs); } $attribute = $this->getAttribute($attr); if (! is_null($attribute)) { $attribute->setMinValueCount($value); } } /** * Set the LDAP type property for an attribute * * @param object Attribute * @param string (MUST,MAY,OPTIONAL) */ private function setAttrLDAPtype($attr,$value) { if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) { debug_log('Entered (%%)', 5, 0, __FILE__, __LINE__, __METHOD__, $fargs); } $attribute = $this->getAttribute($attr); if (is_null($attribute)) { $attribute = $this->addAttribute($attr, array('values' => array())); } $attribute->setLDAPtype($value); } /** * OnChangeAdd javascript processing * @param $origin * @param $value */ public function OnChangeAdd($origin,$value) { if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS')) { debug_log('Entered (%%)', 5, 0, __FILE__, __LINE__, __METHOD__, $fargs); } $attribute = $this->getAttribute($origin); if (preg_match('/^=(\w+)\((.*)\)$/',$value,$matches)) { $command = $matches[1]; $arg = $matches[2]; } else { return; } switch ($command) { /* autoFill:string string is a literal string, and may contain many fields like %attr|start-end/flags% to substitute values read from other fields. |start-end is optional, but must be present if the k flag is used. /flags is optional. flags may be: T: Read display text from selection item (drop-down list), otherwise, read the value of the field For fields that aren't selection items, /T shouldn't be used, and the field value will always be read. k: Tokenize: If the "k" flag is not given: A |start-end instruction will perform a sub-string operation upon the value of the attr, passing character positions start-end through. start can be 0 for first character, or any other integer. end can be 0 for last character, or any other integer for a specific position. If the "k" flag is given: The string read will be split into fields, using : as a delimiter "start" indicates which field number to pass through. K: The string read will be split into fields, using ' ' as a delimiter "start" indicates which field number to pass through. l: Make the result lower case. U: Make the result upper case. */ case 'autoFill': if (! preg_match('/;/',$arg)) { system_message(array( 'title'=>_('Problem with autoFill() in template'), 'body'=>sprintf('%s (%s)',_('There is only 1 argument, when there should be two'),$attribute->getName(false)), 'type'=>'warn')); return; } [$attr,$string] = preg_split('(([^,]+);(.*))',$arg,-1,PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); preg_match_all('/%(\w+)(\|[0-9]*-[0-9]*)?(\/[KklTUA]+)?%/U',$string,$matchall); //print"
";print_r($matchall); //0 = highlevel match, 1 = attr, 2 = subst, 3 = mod

				if (! isset($attribute->js['autoFill']))
				{
					$attribute->js['autoFill'] = '';
				}

				$formula = $string;
				$formula = preg_replace('/^([^%])/','\'$1',$formula);
				$formula = preg_replace('/([^%])$/','$1\'',$formula);

				# Check that our attributes match our schema attributes.
				foreach ($matchall[1] as $index => $checkattr) {
					$sattr = $this->getServer()->getSchemaAttribute($checkattr);

					# If the attribute is the same as in the XML file, then dont need to do anything.
					if (! $sattr || ! strcasecmp($sattr->getName(),$checkattr))
					{
						continue;
					}

					$formula = preg_replace("/$checkattr/",$sattr->getName(),$formula);
					$matchall[1][$index] = $sattr->getName();
				}

				$elem_id = 0;

				foreach ($matchall[0] as $index => $null) {
					$match_attr = strtolower($matchall[1][$index]);
					$match_subst = $matchall[2][$index];
					$match_mod = $matchall[3][$index];

					$substrarray = array();

					if (! isset($varcount[$match_attr]))
					{
						$varcount[$match_attr] = 0;
					}
					else
					{
						$varcount[$match_attr]++;
					}

					$js_match_attr = $match_attr;
					$match_attr = $js_match_attr.'xx'.$varcount[$match_attr];

					$formula = preg_replace('/%'.$js_match_attr.'([|\/%])/i','%'.$match_attr.'$1',$formula,1);

					$attribute->js['autoFill'] .= sprintf("  var %s;\n",$match_attr);
					$attribute->js['autoFill'] .= sprintf(
							"  var elem$elem_id = document.getElementById(pre+'%s'+suf);\n".
							"  if (!elem$elem_id) return;\n", $js_match_attr);

					if (strstr($match_mod,'T')) {
						$attribute->js['autoFill'] .= sprintf("  %s = elem$elem_id.options[elem$elem_id.selectedIndex].text;\n",
							$match_attr);
					} else {
						$attribute->js['autoFill'] .= sprintf("  %s = elem$elem_id.value;\n",$match_attr);
					}

					$elem_id++;

					if (strstr($match_mod,'k')) {
						preg_match_all('/([0-9]+)/',trim($match_subst),$substrarray);
						if (isset($substrarray[1][0])) {
							$tok_idx = $substrarray[1][0];
						} else {
							$tok_idx = '0';
						}
						$attribute->js['autoFill'] .= sprintf("   %s = %s.split(':')[%s];\n",$match_attr,$match_attr,$tok_idx);

					} elseif (strstr($match_mod,'K')) {
						preg_match_all('/([0-9]+)/',trim($match_subst),$substrarray);
						if (isset($substrarray[1][0])) {
							$tok_idx = $substrarray[1][0];
						} else {
							$tok_idx = '0';
						}
						$attribute->js['autoFill'] .= sprintf("   %s = %s.split(' ')[%s];\n",$match_attr,$match_attr,$tok_idx);

					} else {
						preg_match_all('/([0-9]*)-([0-9]*)/',trim($match_subst),$substrarray);
						if ((isset($substrarray[1][0]) && $substrarray[1][0]) || (isset($substrarray[2][0]) && $substrarray[2][0])) {
							$attribute->js['autoFill'] .= sprintf("   %s = %s.substr(%s,%s);\n",
								$match_attr,$match_attr,
								$substrarray[1][0] ?: '0',
								$substrarray[2][0] ?: sprintf('%s.length',$match_attr));
						}
					}

					if (strstr($match_mod,'l')) {
						$attribute->js['autoFill'] .= sprintf("   %s = %s.toLowerCase();\n",$match_attr,$match_attr);
					}
					if (strstr($match_mod,'U')) {
						$attribute->js['autoFill'] .= sprintf("   %s = %s.toUpperCase();\n",$match_attr,$match_attr);
					}
					if (strstr($match_mod,'A')) {
						$attribute->js['autoFill'] .= sprintf("   %s = toAscii(%s);\n",$match_attr,$match_attr);
					}

					# Matchfor only entry without modifiers.
					$formula = preg_replace('/^%('.$match_attr.')%$/U','$1 + \'\'',$formula);
					# Matchfor only entry with modifiers.
					$formula = preg_replace('/^%('.$match_attr.')(\|[0-9]*-[0-9]*)?(\/[KklTUA]+)?%$/U','$1 + \'\'',$formula);
					# Matchfor begining entry.
					$formula = preg_replace('/^%('.$match_attr.')(\|[0-9]*-[0-9]*)?(\/[KklTUA]+)?%/U','$1 + \'',$formula);
					# Matchfor ending entry.
					$formula = preg_replace('/%('.$match_attr.')(\|[0-9]*-[0-9]*)?(\/[KklTUA]+)?%$/U','\' + $1 ',$formula);
					# Match for entries not at begin/end.
					$formula = preg_replace('/%('.$match_attr.')(\|[0-9]*-[0-9]*)?(\/[:lTUA]+)?%/U','\' + $1 + \'',$formula);
					$attribute->js['autoFill'] .= "\n";
				}

				$attribute->js['autoFill'] .= sprintf(" fillRec(pre+'%s'+suf, %s); // %s\n",strtolower($attr),$formula,$string);
				$attribute->js['autoFill'] .= "\n";
				break;

			default: $return = '';
		}
	}

	/**
	 * This functions main purpose is to discover our MUST attributes based on objectclass
	 * definitions in the template file and to discover which of the objectclasses are
	 * STRUCTURAL - without one, creating an entry will just product an LDAP error.
	 */
	private function rebuildTemplateAttrs() {
		if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
		{
			debug_log('Entered (%%)', 5, 0, __FILE__, __LINE__, __METHOD__, $fargs);
		}

		$server = $this->getServer();

		# Collect our structural, MUST & MAY attributes.
		$oclass_processed = array();
		$superclasslist = array();
		$allattrs = array('objectclass');

		foreach ($this->getObjectClasses() as $oclass) {
			# If we get some superclasses - then we'll need to go through them too.
			$supclass = true;
			$inherited = false;

			while ($supclass) {
				$soc = $server->getSchemaObjectClass($oclass);

				if ($soc->getType() == 'structural' && (! $inherited))
				{
					$this->structural_oclass[] = $oclass;
				}

				# Make sure our MUST attributes are marked as such for this template.
				if ($soc->getMustAttrs())
				{
					foreach ($soc->getMustAttrs() as $index => $details)
					{
						$objectclassattr = $details->getName();

						# We add the 'objectClass' attribute, only if it's explicitly in the template attribute list
						if ((strcasecmp('objectClass', $objectclassattr) != 0) ||
							((strcasecmp('objectClass', $objectclassattr) == 0) && ( ! is_null($this->getAttribute($objectclassattr)))))
						{

							# Go through the aliases, and ignore any that are already defined.
							$ignore = FALSE;
							$sattr = $server->getSchemaAttribute($objectclassattr);
							foreach ($sattr->getAliases() as $alias)
							{
								if ($this->isAttrType($alias, 'must'))
								{
									$ignore = TRUE;
									break;
								}
							}

							if ($ignore)
							{
								continue;
							}

							$this->setAttrLDAPtype($sattr->getName(), 'must');
							$this->setMinValueCount($sattr->getName(), 1);

							# We need to mark the attributes as show, except for the objectclass attribute.
							if (strcasecmp('objectClass', $objectclassattr) != 0)
							{
								$attribute = $this->getAttribute($sattr->getName());
								$attribute->show();
							}
						}

						if ( ! in_array($objectclassattr, $allattrs))
						{
							$allattrs[] = $objectclassattr;
						}
					}
				}

				if ($soc->getMayAttrs())
				{
					foreach ($soc->getMayAttrs() as $index => $details)
					{
						$objectclassattr = $details->getName();
						$sattr = $server->getSchemaAttribute($objectclassattr);

						# If it is a MUST attribute, skip to the next one.
						if ($this->isAttrType($objectclassattr, 'must'))
						{
							continue;
						}

						if ( ! $this->isAttrType($objectclassattr, 'may'))
						{
							$this->setAttrLDAPtype($sattr->getName(FALSE), 'may');
						}

						if ( ! in_array($objectclassattr, $allattrs))
						{
							$allattrs[] = $objectclassattr;
						}
					}
				}

				# Keep a list to objectclasses we have processed, so we dont get into a loop.
				$oclass_processed[] = $oclass;
				$supoclasses = $soc->getSupClasses();

				if (count($supoclasses) || count($superclasslist)) {
					foreach ($supoclasses as $supoclass) {
						if (! in_array($supoclass,$oclass_processed))
						{
							$superclasslist[] = $supoclass;
						}
					}

					$oclass = array_shift($superclasslist);
					if ($oclass)
					{
						$inherited = TRUE;
					}
					else
					{
						$supclass = FALSE;
					}

				} else {
					$supclass = false;
				}
			}
		}

		# Check that attributes are defined by an ObjectClass
		foreach ($this->getAttributes(true) as $index => $attribute)
		{
			if ( ! in_array($attribute->getName(), $allattrs) && ( ! array_intersect($attribute->getAliases(), $allattrs))
				&& ( ! in_array_ignore_case('extensibleobject', $this->getObjectClasses()))
				&& ( ! in_array_ignore_case($attribute->getName(), $server->getValue('server', 'custom_attrs'))))
			{
				unset($this->attributes[$index]);

				if ( ! $_SESSION[APPCONFIG]->getValue('appearance', 'hide_template_warning'))
				{
					system_message(array(
						'title' => _('Automatically removed attribute from template'),
						'body' => sprintf('%s: %s %s', $this->getTitle(), $attribute->getName(FALSE), _('removed from template as it is not defined by an ObjectClass')),
						'type' => 'warn'));
				}
			}
		}
	}

	/**
	 * Return an array, that can be passed to ldap_add().
	 * Attributes with empty values will be excluded.
	 * @param bool $attrsOnly
	 * @return array
	 */
	public function getLDAPadd($attrsOnly=false) {
		if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
		{
			debug_log('Entered (%%)', 5, 0, __FILE__, __LINE__, __METHOD__, $fargs);
		}

		$return = array();
		$returnattrs = array();

		if ($attrsOnly && count($returnattrs))
		{
			return $returnattrs;
		}

		foreach ($this->getAttributes(true) as $attribute)
		{
			if ( ! $attribute->isInternal() && count($attribute->getValues()))
			{
				$return[$attribute->getName()] = $attribute->getValues();
				$returnattrs[$attribute->getName()] = $attribute;
			}
		}

		# Ensure that our objectclasses has "top".
		if (isset($return['objectclass']) && ! in_array('top',$return['objectclass']))
		{
			$return['objectclass'][] = 'top';
		}

		if ($attrsOnly)
		{
			return $returnattrs;
		}

		return $return;
	}

	/**
	 * Return an array, that can be passed to ldap_mod_replace().
	 * Only attributes that have changed their value will be returned.
	 *
	 * This function will cache its results, so that it can be called with count() to see
	 * if there are changes, and if they are, the 2nd call will just return the results
	 *
	 * @param boolean Return the attribute objects (useful for a confirmation process), or the modification array for ldap_modify()
	 * @param int $index
	 * @return mixed
	 */
	public function getLDAPmodify($attrsOnly=false,$index=0) {
		if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
		{
			debug_log('Entered (%%)', 5, 0, __FILE__, __LINE__, __METHOD__, $fargs);
		}

		static $return = array();
		static $returnattrs = array();

		if ($attrsOnly && isset($returnattrs[$index]) && count($returnattrs[$index]))
		{
			return $returnattrs[$index];
		}

		$returnattrs[$index] = array();
		$return[$index] = array();

		# If an objectclass is being modified, we need to remove all the orphan attributes that would result.
		if ($this->getAttribute('objectclass')->hasBeenModified()) {
			$attr_to_keep = array();
			$server = $this->getServer();

			# Make sure that there will be a structural object class remaining.
			$haveStructural = false;
			foreach ($this->getAttribute('objectclass')->getValues() as $value) {
				$soc = $server->getSchemaObjectClass($value);

				if ($soc) {
					if ($soc->isStructural())
					{
						$haveStructural = TRUE;
					}

					# While we are looping, workout which attributes these objectclasses define.
					foreach ($soc->getMustAttrs(true) as $value)
					{
						if ( ! in_array($value->getName(), $attr_to_keep))
						{
							$attr_to_keep[] = $value->getName();
						}
					}

					foreach ($soc->getMayAttrs(true) as $value)
					{
						if ( ! in_array($value->getName(), $attr_to_keep))
						{
							$attr_to_keep[] = $value->getName();
						}
					}
				}
			}

			if (! $haveStructural)
			{
				error(_('An entry should have one structural objectClass.'), 'error', 'index.php');
			}

			# Work out the attributes to delete.
			foreach ($this->getAttribute('objectclass')->getRemovedValues() as $value) {
				$soc = $server->getSchemaObjectClass($value);

				foreach ($soc->getMustAttrs() as $value) {
					$attribute = $this->getAttribute($value->getName());

					if ($attribute && (! in_array($value->getName(),$attr_to_keep)) && ($value->getName() != 'objectclass'))
						#array_push($attr_to_delete,$value->getName(false));
					{
						$attribute->setForceDelete();
					}
				}

				foreach ($soc->getMayAttrs() as $value) {
					$attribute = $this->getAttribute($value->getName());

					if ($attribute && (! in_array($value->getName(),$attr_to_keep)) && ($value->getName() != 'objectclass'))
					{
						$attribute->setForceDelete();
					}
				}
			}
		}

		foreach ($this->getAttributes(true) as $attribute)
		{
			if ($attribute->hasBeenModified()
				&& (count(array_diff($attribute->getValues(), $attribute->getOldValues())) || ! count($attribute->getValues())
					|| $attribute->isForceDelete() || (count($attribute->getValues()) != count($attribute->getOldValues()))))
			{
				$returnattrs[$index][$attribute->getName()] = $attribute;
			}
		}

		if ($attrsOnly)
		{
			return $returnattrs[$index];
		}

		foreach ($returnattrs[$index] as $attribute)
		{
			$return[$index][$attribute->getName()] = $attribute->getValues();
		}

		return $return[$index];
	}

	/**
	 * Get the attributes that are marked as force delete
	 * We'll cache this result in the event of multiple calls.
	 */
	public function getForceDeleteAttrs() {
		if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
		{
			debug_log('Entered (%%)', 5, 0, __FILE__, __LINE__, __METHOD__, $fargs);
		}

		static $result = array();

		if (count($result))
		{
			return $result;
		}

		foreach ($this->attributes as $attribute)
		{
			if ($attribute->isForceDelete())
			{
				$result[] = $attribute;
			}
		}

		return $result;
	}

	/**
	 * Get available attributes
	 */
	public function getAvailAttrs() {
		if (DEBUG_ENABLED && (($fargs=func_get_args())||$fargs='NOARGS'))
		{
			debug_log('Entered (%%)', 5, 0, __FILE__, __LINE__, __METHOD__, $fargs);
		}

		$attributes = array();
		$server = $this->getServer();

		# Initialise the Attribute Factory.
		$attribute_factory = new AttributeFactory();

		if (in_array_ignore_case('extensibleobject',$this->getObjectClasses())) {
			foreach ($server->SchemaAttributes() as $sattr) {
				$attribute = $attribute_factory->newAttribute($sattr->getName(),array('values'=>array()),$server->getIndex(),null);
				$attributes[] = $attribute;
			}

		} else {
			$attrs = array();

			foreach ($this->getObjectClasses() as $oc) {
				$soc = $server->getSchemaObjectClass($oc);
				$attrs = array_merge($attrs,$soc->getMustAttrNames(true),$soc->getMayAttrNames(true));
				$attrs = array_unique($attrs);
			}

			foreach ($attrs as $attr)
			{
				if (is_null($this->getAttribute($attr)))
				{
					$attribute = $attribute_factory->newAttribute($attr, array('values' => array()), $server->getIndex(), NULL);
					$attributes[] = $attribute;
				}
			}
		}

		masort($attributes,'name');
		return $attributes;
	}

	public function isNoLeaf() {
		return $this->noleaf;
	}

	public function sort() {
		usort($this->attributes,'sortAttrs');
	}
}