1<?php
2/**
3 * Classes and functions for the template engine.
4 *
5 * Templates are either:
6 * + Creating or Editing, (a Container or DN passed to the object)
7 * + A predefined template, or a default template (template ID passed to the object)
8 *
9 * The template object will know which attributes are mandatory (MUST
10 * attributes) and which attributes are optional (MAY attributes). It will also
11 * contain a list of optional attributes. These are attributes that the schema
12 * will allow data for (they are MAY attributes), but the template has not
13 * included a definition for them.
14 *
15 * The template object will be invalidated if it does contain the necessary
16 * items (objectClass, MUST attributes, etc) to make a successful LDAP update.
17 *
18 * @author The phpLDAPadmin development team
19 * @package phpLDAPadmin
20 */
21
22/**
23 * Template Class
24 *
25 * @package phpLDAPadmin
26 * @subpackage Templates
27 * @todo RDN attributes should be treated as MUST attributes even though the schema marks them as MAY
28 * @todo RDN attributes need to be checked that are included in the schema, otherwise mark it is invalid
29 * @todo askcontainer is no longer used?
30 */
31class Template extends xmlTemplate {
32	# If this template visible on the template choice list
33	private $visible = true;
34	# Is this template valid after parsing the XML file
35	private $invalid = false;
36	private $invalid_admin = false;
37	private $invalid_reason;
38	# The TEMPLATE structural objectclasses
39	protected $structural_oclass = array();
40	protected $description = '';
41	# Is this a read-only template (only valid in modification templates)
42	private $readonly = false;
43
44	# If this is set, it means we are editing an entry.
45	private $dn;
46	# Where this template will store its data
47	protected $container;
48	# Does this template prohibit children being created
49	private $noleaf = false;
50	# A regexp that determines if this template is valid in the container.
51	private $regexp;
52	# Template Title
53	public $title;
54	# Icon for the template
55	private $icon;
56	# Template RDN attributes
57	private $rdn;
58
59	public function __construct($server_id,$name=null,$filename=null,$type=null,$id=null) {
60		parent::__construct($server_id,$name,$filename,$type,$id);
61
62		# If this is the default template, we might disable leafs by default.
63		if (is_null($filename))
64			$this->noleaf = $_SESSION[APPCONFIG]->getValue('appearance','disable_default_leaf');
65	}
66
67	public function __clone() {
68		# We need to clone our attributes, when passing back a template with getTemplate
69		foreach ($this->attributes as $key => $value)
70			$this->attributes[$key] = clone $value;
71	}
72
73	/**
74	 * Main processing to store the template.
75	 *
76	 * @param xmldata Parsed xmldata from xml2array object
77	 */
78	protected function storeTemplate($xmldata) {
79		$server = $this->getServer();
80		$objectclasses = array();
81
82		foreach ($xmldata['template'] as $xml_key => $xml_value) {
83			switch ($xml_key) {
84				# Build our object Classes from the DN and Template.
85				case ('objectclasses'):
86					if (isset($xmldata['template'][$xml_key]['objectclass']))
87						if (is_array($xmldata['template'][$xml_key]['objectclass'])) {
88							foreach ($xmldata['template'][$xml_key]['objectclass'] as $index => $details) {
89
90								# XML files with only 1 objectClass dont have a numeric index.
91								$soc = $server->getSchemaObjectClass(strtolower($details));
92
93								# If we havent recorded this objectclass already, do so now.
94								if (is_object($soc) && ! in_array($soc->getName(),$objectclasses))
95									array_push($objectclasses,$soc->getName(false));
96
97								elseif (! is_object($soc) && ! $_SESSION[APPCONFIG]->getValue('appearance','hide_template_warning'))
98									system_message(array(
99										'title'=>('Automatically removed objectClass from template'),
100										'body'=>sprintf('%s: <b>%s</b> %s',$this->getTitle(),$details,('removed from template as it is not defined in the schema')),
101										'type'=>'warn'));
102							}
103
104						} else {
105							# XML files with only 1 objectClass dont have a numeric index.
106							$soc = $server->getSchemaObjectClass(strtolower($xmldata['template'][$xml_key]['objectclass']));
107
108							# If we havent recorded this objectclass already, do so now.
109							if (is_object($soc) && ! in_array($soc->getName(),$objectclasses))
110								array_push($objectclasses,$soc->getName(false));
111						}
112
113					break;
114
115				# Build our attribute list from the DN and Template.
116				case ('attributes'):
117					if (is_array($xmldata['template'][$xml_key])) {
118						foreach ($xmldata['template'][$xml_key] as $tattrs)
119							foreach ($tattrs as $index => $details) {
120								# If there is no schema definition for the attribute, it will be ignored.
121								if ($sattr = $server->getSchemaAttribute($index))
122									if (is_null($this->getAttribute($sattr->getName())))
123										$this->addAttribute($sattr->getName(),$details,'XML');
124							}
125
126						masort($this->attributes,'order');
127					}
128
129					break;
130
131				default:
132					# Some key definitions need to be an array, some must not be:
133					$allowed_arrays = array('rdn');
134					$storelower = array('rdn');
135					$storearray = array('rdn');
136
137					# Items that must be stored lowercase
138					if (in_array($xml_key,$storelower))
139						if (is_array($xml_value))
140							foreach ($xml_value as $index => $value)
141								$xml_value[$index] = strtolower($value);
142						else
143							$xml_value = strtolower($xml_value);
144
145					# Items that must be stored as arrays
146					if (in_array($xml_key,$storearray) && ! is_array($xml_value))
147						$xml_value = array($xml_value);
148
149					# Items that should not be an array
150					if (! in_array($xml_key,$allowed_arrays) && is_array($xml_value)) {
151						debug_dump(array(__METHOD__,'key'=>$xml_key,'value'=>$xml_value));
152						error(sprintf(('In the XML file (%s), [%s] is an array, it must be a string.'),
153							$this->filename,$xml_key),'error');
154					}
155
156					$this->$xml_key = $xml_value;
157
158					if ($xml_key == 'invalid' && $xml_value)
159						$this->setInvalid(('Disabled by XML configuration'),true);
160			}
161		}
162
163		if (! count($objectclasses)) {
164			$this->setInvalid(('ObjectClasses in XML dont exist in LDAP server.'));
165			return;
166
167		} else {
168			$attribute = $this->addAttribute('objectClass',array('values'=>$objectclasses),'XML');
169			$attribute->justModified();
170			$attribute->setRequired();
171			$attribute->hide();
172		}
173
174		$this->rebuildTemplateAttrs();
175
176		# Check we have some manditory items.
177		foreach (array('rdn','structural_oclass','visible') as $key) {
178			if (! isset($this->$key)
179				|| (! is_array($this->$key) && ! trim($this->$key))) {
180
181				$this->setInvalid(sprintf(('Missing %s in the XML file.'),$key));
182				break;
183			}
184		}
185
186		# Mark our RDN attributes as RDN
187		$counter = 1;
188		foreach ($this->rdn as $key) {
189			if ((is_null($attribute = $this->getAttribute($key))) && (in_array_ignore_case('extensibleobject',$this->getObjectClasses()))) {
190				$attribute = $this->addAttribute($key,array('values'=>array()));
191				$attribute->show();
192			}
193
194			if (! is_null($attribute))
195				$attribute->setRDN($counter++);
196			elseif ($this->isType('creation'))
197				$this->setInvalid(sprintf(('Missing RDN attribute %s in the XML file.'),$key));
198		}
199	}
200
201	/**
202	 * Is default templates enabled?
203	 * This will disable the default template from the engine.
204	 *
205	 * @return boolean
206	 */
207	protected function hasDefaultTemplate() {
208		if ($_SESSION[APPCONFIG]->getValue('appearance','disable_default_template'))
209			return false;
210		else
211			return true;
212	}
213
214	/**
215	 * Return the templates of type (creation/modification)
216	 *
217	 * @param $string type - creation/modification
218	 * @return array - Array of templates of that type
219	 */
220	protected function readTemplates($type) {
221		$template_xml = new Templates($this->server_id);
222		return $template_xml->getTemplates($type);
223	}
224
225	/**
226	 * This function will perform the following intialisation steps:
227	 * + If a DN is set, query the ldap and load the object
228	 * + Read our $_REQUEST variable and set the values
229	 * After this action, the template should self describe as to whether it is an update, create
230	 * or delete.
231	 * (OLD values are IGNORED, we will have got them when we build this object from the LDAP server DN.)
232	 */
233	public function accept($makeVisible=false) {
234		$server = $this->getServer();
235
236		# If a DN is set, then query the LDAP server for the details.
237		if ($this->dn) {
238			if (! $server->dnExists($this->dn))
239				system_message(array(
240					'title'=>__METHOD__,
241					'body'=>sprintf('DN (%s) didnt exist in LDAP?',$this->dn),
242					'type'=>'info'));
243
244			$rdnarray = rdn_explode(strtolower(get_rdn(dn_escape($this->dn))));
245
246			$counter = 1;
247			foreach ($server->getDNAttrValues($this->dn,null,LDAP_DEREF_NEVER,array_merge(array('*'),$server->getValue('server','custom_attrs'))) as $attr => $values) {
248				# We ignore DNs.
249				if ($attr == 'dn')
250					continue;
251
252				$attribute = $this->getAttribute($attr);
253
254				if (is_null($attribute))
255					$attribute = $this->addAttribute($attr,array('values'=>$values));
256				else
257					if ($attribute->getValues()) {
258						# Override values to those that are defined in the XML file.
259						if ($attribute->getSource() != 'XML')
260							$attribute->setValue(array_values($values));
261						else
262							$attribute->setOldValue(array_values($values));
263
264					} else
265						$attribute->initValue(array_values($values));
266
267				# Work out the RDN attributes
268				foreach ($attribute->getValues() as $index => $value)
269					if (in_array(sprintf('%s=%s',
270						$attribute->getName(),strtolower($attribute->getValue($index))),$rdnarray))
271						$attribute->setRDN($counter++);
272
273				if ($makeVisible)
274					$attribute->show();
275			}
276
277			# Get the Internal Attributes
278			foreach ($server->getDNAttrValues($this->dn,null,LDAP_DEREF_NEVER,array_merge(array('+'),$server->getValue('server','custom_sys_attrs'))) as $attr => $values) {
279				$attribute = $this->getAttribute($attr);
280
281				if (is_null($attribute))
282					$attribute = $this->addAttribute($attr,array('values'=>$values));
283				else
284					if ($attribute->getValues())
285						$attribute->setValue(array_values($values));
286					else
287						$attribute->initValue(array_values($values));
288
289				if (! in_array_ignore_case($attribute->getName(),$server->getValue('server','custom_attrs')))
290					$attribute->setInternal();
291			}
292
293		# If this is the default template, and our $_REQUEST has defined our objectclass, then query the schema to get the attributes
294		} elseif ($this->container) {
295			if ($this->isType('default') && ! count($this->getAttributes(true)) && isset($_REQUEST['new_values']['objectclass'])) {
296				$attribute = $this->addAttribute('objectclass',array('values'=>$_REQUEST['new_values']['objectclass']));
297				$attribute->justModified();
298				$this->rebuildTemplateAttrs();
299				unset($_REQUEST['new_values']['objectclass']);
300			}
301
302		} elseif (get_request('create_base')) {
303			if (get_request('rdn')) {
304				$rdn = explode('=',get_request('rdn'));
305				$attribute = $this->addAttribute($rdn[0],array('values'=>array($rdn[1])));
306				$attribute->setRDN(1);
307			}
308
309		} else {
310			debug_dump_backtrace('No DN or CONTAINER?',1);
311		}
312
313		# Read in our new values.
314		foreach (array('new_values') as $key) {
315			if (isset($_REQUEST[$key]))
316				foreach ($_REQUEST[$key] as $attr => $values) {
317					# If it isnt an array, silently ignore it.
318					if (! is_array($values))
319						continue;
320
321					# If _REQUEST['skip_array'] with this attr set, we'll ignore this new_value
322					if (isset($_REQUEST['skip_array'][$attr]) && $_REQUEST['skip_array'][$attr] == 'on')
323						continue;
324
325					# Prune out entries with a blank value.
326					foreach ($values as $index => $value)
327						if (! strlen(trim($value)))
328							unset($values[$index]);
329
330					$attribute = $this->getAttribute($attr);
331					# If the attribute is null, then no attribute exists, silently ignore it (unless this is the default template)
332					if (is_null($attribute) && (! $this->isType('default') && ! $this->isType(null)))
333						continue;
334
335					# If it is a binary attribute, the post should have base64 encoded the value, we'll need to reverse that
336					if ($server->isAttrBinary($attr))
337						foreach ($values as $index => $value)
338							$values[$index] = base64_decode($value);
339
340					if (is_null($attribute)) {
341						$attribute = $this->addAttribute($attr,array('values'=>$values));
342
343						if (count($values))
344							$attribute->justModified();
345
346					} else
347						$attribute->setValue(array_values($values));
348				}
349
350			# Read in our new binary values
351			if (isset($_FILES[$key]['name']))
352				foreach ($_FILES[$key]['name'] as $attr => $values) {
353					$new_values = array();
354
355					foreach ($values as $index => $details) {
356						# Ignore empty files
357						if (! $_FILES[$key]['size'][$attr][$index])
358							continue;
359
360						if (! is_uploaded_file($_FILES[$key]['tmp_name'][$attr][$index])) {
361							if (isset($_FILES[$key]['error'][$attr][$index]))
362								switch($_FILES[$key]['error'][$attr][$index]) {
363
364									# No error; possible file attack!
365									case 0:
366										$msg = _('Security error: The file being uploaded may be malicious.');
367										break;
368
369									# Uploaded file exceeds the upload_max_filesize directive in php.ini
370									case 1:
371										$msg = _('The file you uploaded is too large. Please check php.ini, upload_max_size setting');
372										break;
373
374									# Uploaded file exceeds the MAX_FILE_SIZE directive specified in the html form
375									case 2:
376										$msg = _('The file you uploaded is too large. Please check php.ini, upload_max_size setting');
377										break;
378
379									# Uploaded file was only partially uploaded
380									case 3:
381										$msg = _('The file you selected was only partially uploaded, likley due to a network error.');
382										break;
383
384									# No file was uploaded
385									case 4:
386										$msg = _('You left the attribute value blank. Please go back and try again.');
387										break;
388
389									# A default error, just in case! :)
390									default:
391										$msg = _('Security error: The file being uploaded may be malicious.');
392										break;
393								}
394
395							else
396								$msg = _('Security error: The file being uploaded may be malicious.');
397
398							system_message(array(
399								'title'=>_('Error'),'body'=>$msg,'type'=>'warn'));
400
401						} else {
402							$binaryfile = array();
403							$binaryfile['name'] = $_FILES[$key]['tmp_name'][$attr][$index];
404							$binaryfile['handle'] = fopen($binaryfile['name'],'r');
405							$binaryfile['data'] = fread($binaryfile['handle'],filesize($binaryfile['name']));
406							fclose($binaryfile['handle']);
407
408							$new_values[$index] = $binaryfile['data'];
409						}
410					}
411
412					if (count($new_values)) {
413						$attribute = $this->getAttribute($attr);
414
415						if (is_null($attribute))
416							$attribute = $this->addAttribute($attr,array('values'=>$new_values));
417						else
418							foreach ($new_values as $value)
419								$attribute->addValue($value);
420
421						$attribute->justModified();
422					}
423				}
424		}
425
426		# If there are any single item additions (from the add_attr form for example)
427		if (isset($_REQUEST['single_item_attr'])) {
428			if (isset($_REQUEST['single_item_value'])) {
429				if (! is_array($_REQUEST['single_item_value']))
430					$values = array($_REQUEST['single_item_value']);
431				else
432					$values = $_REQUEST['single_item_value'];
433
434			} elseif (isset($_REQUEST['binary'])) {
435				/* Special case for binary attributes (like jpegPhoto and userCertificate):
436				 * we must go read the data from the file and override $_REQUEST['single_item_value'] with the
437				 * binary data. Secondly, we must check if the ";binary" option has to be appended to the name
438				 * of the attribute. */
439
440				if ($_FILES['single_item_value']['size'] === 0)
441					system_message(array(
442						'title'=>_('Error'),
443						'body'=>sprintf('%s %s',_('The file you chose is either empty or does not exist.'),_('Please go back and try again.')),
444						'type'=>'warn'));
445
446				else {
447					if (! is_uploaded_file($_FILES['single_item_value']['tmp_name'])) {
448						if (isset($_FILES['single_item_value']['error']))
449							switch($_FILES['single_item_value']['error']) {
450
451								# No error; possible file attack!
452								case 0:
453									$msg = _('Security error: The file being uploaded may be malicious.');
454									break;
455
456								# Uploaded file exceeds the upload_max_filesize directive in php.ini
457								case 1:
458									$msg = _('The file you uploaded is too large. Please check php.ini, upload_max_size setting');
459									break;
460
461								# Uploaded file exceeds the MAX_FILE_SIZE directive specified in the html form
462								case 2:
463									$msg = _('The file you uploaded is too large. Please check php.ini, upload_max_size setting');
464									break;
465
466								# Uploaded file was only partially uploaded
467								case 3:
468									$msg = _('The file you selected was only partially uploaded, likley due to a network error.');
469									break;
470
471								# No file was uploaded
472								case 4:
473									$msg = _('You left the attribute value blank. Please go back and try again.');
474									break;
475
476								# A default error, just in case! :)
477								default:
478									$msg = _('Security error: The file being uploaded may be malicious.');
479									break;
480							}
481
482						else
483							$msg = _('Security error: The file being uploaded may be malicious.');
484
485						system_message(array(
486							'title'=>_('Error'),'body'=>$msg,'type'=>'warn'),'index.php');
487					}
488
489					$binaryfile = array();
490					$binaryfile['name'] = $_FILES['single_item_value']['tmp_name'];
491					$binaryfile['handle'] = fopen($binaryfile['name'],'r');
492					$binaryfile['data'] = fread($binaryfile['handle'],filesize($binaryfile['name']));
493					fclose($binaryfile['handle']);
494
495					$values = array($binaryfile['data']);
496				}
497			}
498
499			if (count($values)) {
500				$attribute = $this->getAttribute($_REQUEST['single_item_attr']);
501
502				if (is_null($attribute))
503					$attribute = $this->addAttribute($_REQUEST['single_item_attr'],array('values'=>$values));
504				else
505					$attribute->setValue(array_values($values));
506
507				$attribute->justModified();
508			}
509		}
510
511		# If this is the default creation template, we need to set some additional values
512		if ($this->isType('default') && $this->getContext() == 'create') {
513			# Load our schema, based on the objectclasses that may have already been defined.
514			if (! get_request('create_base'))
515				$this->rebuildTemplateAttrs();
516
517			# Set the RDN attribute
518			$counter = 1;
519			foreach (get_request('rdn_attribute','REQUEST',false,array()) as $key => $value) {
520				$attribute = $this->getAttribute($value);
521
522				if (! is_null($attribute))
523					$attribute->setRDN($counter++);
524
525				else {
526					system_message(array(
527						'title'=>_('No RDN attribute was selected.'),
528						'body'=>_('No RDN attribute was selected.'),
529						'type'=>'warn'),'index.php');
530
531					die();
532				}
533			}
534		}
535	}
536
537	/**
538	 * Set the DN for this template, if we are editing entries
539	 *
540	 * @param dn The DN of the entry
541	 */
542	public function setDN($dn) {
543		if (isset($this->container))
544			system_message(array(
545				'title'=>__METHOD__,
546				'body'=>'CONTAINER set while setting DN',
547				'type'=>'info'));
548
549		$this->dn = $dn;
550	}
551
552	/**
553	 * Set the RDN attributes
554	 * Given an RDN, mark the attributes as RDN attributes. If there is no defined attribute,
555	 * then the remaining RDNs will be returned.
556	 *
557	 * @param RDN
558	 * @return RDN attributes not processed
559	 */
560	public function setRDNAttributes($rdn) {
561		# Setup to work out our RDN.
562		$rdnarray = rdn_explode($rdn);
563
564		$counter = 1;
565		foreach ($this->getAttributes(true) as $attribute)
566			foreach ($rdnarray as $index => $rdnattr) {
567				list($attr,$value) = explode('=',$rdnattr);
568
569				if (strtolower($attr) == $attribute->getName()) {
570					$attribute->setRDN($counter++);
571					unset($rdnarray[$index]);
572				}
573			}
574
575		return $rdnarray;
576	}
577
578	/**
579	 * Display the DN for this template entry. If the DN is not set (creating a new entry), then
580	 * a generated DN will be produced, taken from the RDN and the CONTAINER details.
581	 *
582	 * @return dn
583	 */
584	public function getDN() {
585		if ($this->dn)
586			return $this->dn;
587
588		# If DN is not set, our DN will be made from our RDN and Container.
589		elseif ($this->getRDN() && $this->getContainer())
590			return sprintf('%s,%s',$this->getRDN(),$this->GetContainer());
591
592		# If container is not set, we're probably creating the base
593		elseif ($this->getRDN() && get_request('create_base'))
594			return $this->getRDN();
595	}
596
597	public function getDNEncode($url=true) {
598		// @todo Be nice to do all this in 1 location
599		if ($url)
600			return urlencode(preg_replace('/%([0-9a-fA-F]+)/',"%25\\1",$this->getDN()));
601		else
602			return preg_replace('/%([0-9a-fA-F]+)/',"%25\\1",$this->getDN());
603	}
604
605	/**
606	 * Set the container for this template, if we are creating entries
607	 *
608	 * @param dn The DN of the container
609	 * @todo Trigger a query to the LDAP server and generate an error if the container doesnt exist
610	 */
611	public function setContainer($container) {
612		if (isset($this->dn))
613			system_message(array(
614				'title'=>__METHOD__,
615				'body'=>'DN set while setting CONTAINER',
616				'type'=>'info'));
617
618		$this->container = $container;
619	}
620
621	/**
622	 * Get the DN of the container for this entry
623	 *
624	 * @return dn DN of the container
625	 */
626	public function getContainer() {
627		return $this->container;
628	}
629
630	public function getContainerEncode($url=true) {
631		// @todo Be nice to do all this in 1 location
632		if ($url)
633			return urlencode(preg_replace('/%([0-9a-fA-F]+)/',"%25\\1",$this->container));
634		else
635			return preg_replace('/%([0-9a-fA-F]+)/',"%25\\1",$this->container);
636	}
637
638	/**
639	 * Copy a DN
640	 */
641	public function copy($template,$rdn,$asnew=false) {
642		$rdnarray = rdn_explode($rdn);
643
644		$counter = 1;
645		foreach ($template->getAttributes(true) as $sattribute) {
646			$attribute = $this->addAttribute($sattribute->getName(false),array('values'=>$sattribute->getValues()));
647
648			# Set our new RDN, and its values
649			if (is_null($attribute)) {
650				debug_dump_backtrace('Attribute is null, it probably doesnt exist in the destination server?');
651
652			} else {
653
654				# Mark our internal attributes.
655				if ($sattribute->isInternal())
656					$attribute->setInternal();
657
658				$modified = false;
659				foreach ($rdnarray as $index => $rdnattr) {
660					list($attr,$value) = explode('=',$rdnattr);
661					if (strtolower($attr) == $attribute->getName()) {
662
663						# If this is already marked as an RDN, then this multivalue RDN was updated on a previous loop
664						if (! $modified) {
665							$attribute->setValue(array($value));
666							$attribute->setRDN($counter++);
667							$modified = true;
668
669						} else {
670							$attribute->addValue($value);
671						}
672
673						# This attribute has been taken care of, we'll drop it from our list.
674						unset($rdnarray[$index]);
675					}
676				}
677			}
678
679			// @todo If this is a Jpeg Attribute, we need to mark it read only, since it cant be deleted like text attributes can
680			if (strcasecmp(get_class($attribute),'jpegAttribute') == 0)
681				$attribute->setReadOnly();
682		}
683
684		# If we have any RDN values left over, there werent in the original entry and need to be added.
685		foreach ($rdnarray as $rdnattr) {
686			list($attr,$value) = explode('=',$rdnattr);
687
688			$attribute = $this->addAttribute($attr,array('values'=>array($value)));
689
690			if (is_null($attribute))
691				debug_dump_backtrace('Attribute is null, it probably doesnt exist in the destination server?');
692			else
693				$attribute->setRDN($counter++);
694		}
695
696		# If we are copying into a new entry, we need to discard all the "old values"
697		if ($asnew)
698			foreach ($this->getAttributes(true) as $sattribute)
699				$sattribute->setOldValue(array());
700	}
701
702	/**
703	 * Get Attributes by LDAP type
704	 * This function will return a list of attributes by LDAP type (MUST,MAY).
705	 *
706	 * @return array Array of attributes.
707	 */
708	function getAttrbyLdapType($type) {
709		$result = array();
710
711		foreach ($this->attributes as $index => $attribute) {
712			if ($attribute->getLDAPtype() == strtolower($type))
713				array_push($result,$attribute->getName());
714		}
715
716		return $result;
717	}
718
719	/**
720	 * Return true if this is a MUST,MAY attribute
721	 */
722	function isAttrType($attr,$type) {
723		if (in_array(strtolower($attr),$this->getAttrbyLdapType($type)))
724			return true;
725		else
726			return false;
727	}
728
729	/**
730	 * Return the attributes that comprise the RDN.
731	 *
732	 * @return array Array of RDN objects
733	 */
734	private function getRDNObjects() {
735		$return = array();
736
737		foreach ($this->attributes as $attribute)
738			if ($attribute->isRDN())
739				array_push($return,$attribute);
740
741		masort($return,'rdn');
742		return $return;
743	}
744
745	/**
746	 * Get all the RDNs for this template, in RDN order.
747	 *
748	 * @return array RDNs in order.
749	 */
750	public function getRDNAttrs() {
751		$return = array();
752
753		foreach ($this->getRDNObjects() as $attribute) {
754			# We'll test if two RDN's have the same number (we cant test anywhere else)
755			if (isset($return[$attribute->isRDN()]) && $this->getType() == 'creation')
756				system_message(array(
757					'title'=>('RDN attribute sequence already defined'),
758					'body'=>sprintf('%s %s',
759						sprintf(('There is a problem in template [%s].'),$this->getName()),
760						sprintf(('RDN attribute sequence [%s] is already used by attribute [%s] and cant be used by attribute [%s] also.'),
761							$attribute->isRDN(),$return[$attribute->isRDN()],$attribute->getName())),
762					'type'=>'error'),'index.php');
763
764			$return[$attribute->isRDN()] = $attribute->getName();
765		}
766
767		return $return;
768	}
769
770	/**
771	 * Return the RDN for this template. If the DN is already defined, then the RDN will be calculated from it.
772	 * If the DN is not set, then the RDN will be calcuated from the template attribute definitions
773	 *
774	 * @return rdn RDN for this template
775	 */
776	public function getRDN() {
777		# If the DN is set, then the RDN will be calculated from it.
778		if ($this->dn)
779			return get_rdn($this->dn);
780
781		$rdn = '';
782
783		foreach ($this->getRDNObjects() as $attribute) {
784			$vals = $attribute->getValues();
785
786			# If an RDN attribute has no values, return with an empty string. The calling script should handle this.
787			if (! count($vals))
788				return '';
789
790			foreach ($vals as $val)
791				$rdn .= sprintf('%s=%s+',$attribute->getName(false),$val);
792		}
793
794		# Chop the last plus sign off when returning
795		return preg_replace('/\+$/','',$rdn);
796	}
797
798	/**
799	 * Return the attribute name part of the RDN
800	 */
801	public function getRDNAttributeName() {
802		$attr = array();
803
804		if ($this->getDN()) {
805			$i = strpos($this->getDN(),',');
806			if ($i !== false) {
807				$attrs = explode('\+',substr($this->getDN(),0,$i));
808				foreach ($attrs as $id => $attr) {
809					list ($name,$value) = explode('=',$attr);
810					$attrs[$id] = $name;
811				}
812
813				$attr = array_unique($attrs);
814			}
815		}
816
817		return $attr;
818	}
819
820	/**
821	 * Determine the type of template this is
822	 */
823	public function getContext() {
824		if ($this->getContainer() && get_request('cmd','REQUEST') == 'copy')
825			return 'copyasnew';
826		elseif ($this->getContainer() || get_request('create_base'))
827			return 'create';
828		elseif ($this->getDN())
829			return 'edit';
830		else
831			return 'unknown';
832	}
833
834	/**
835	 * Test if the template is visible
836	 *
837	 * @return boolean
838	 */
839	public function isVisible() {
840		return $this->visible;
841	}
842
843	public function setVisible() {
844		$this->visible = true;
845	}
846
847	public function setInvisible() {
848		$this->visible = false;
849	}
850
851	public function getRegExp() {
852		return $this->regexp;
853	}
854
855	/**
856	 * Test if this template has been marked as a read-only template
857	 */
858	public function isReadOnly() {
859		if ((($this->getContext() == 'edit') && $this->readonly) || $this->getServer()->isReadOnly())
860			return true;
861		else
862			return false;
863	}
864
865	/**
866	 * Get the attribute entries
867	 *
868	 * @param boolean Include the optional attributes
869	 * @return array Array of attributes
870	 */
871	public function getAttributes($optional=false) {
872		if ($optional)
873			return $this->attributes;
874
875		$result = array();
876		foreach ($this->attributes as $attribute) {
877			if (! $attribute->isRequired())
878				continue;
879
880			array_push($result,$attribute);
881		}
882
883		return $result;
884	}
885
886	/**
887	 * Return a list of attributes that should be shown
888	 */
889	public function getAttributesShown() {
890		$result = array();
891
892		foreach ($this->attributes as $attribute)
893			if ($attribute->isVisible())
894				array_push($result,$attribute);
895
896		return $result;
897	}
898
899	/**
900	 * Return a list of the internal attributes
901	 */
902	public function getAttributesInternal() {
903		$result = array();
904
905		foreach ($this->attributes as $attribute)
906			if ($attribute->isInternal())
907				array_push($result,$attribute);
908
909		return $result;
910	}
911
912	/**
913	 * Return the objectclasses defined in this template
914	 *
915	 * @return array Array of Objects
916	 */
917	public function getObjectClasses() {
918		$attribute = $this->getAttribute('objectclass');
919		if ($attribute)
920			return $attribute->getValues();
921		else
922			return array();
923	}
924
925	/**
926	 * Get template icon
927	 */
928	public function getIcon() {
929		return isset($this->icon) ? sprintf('%s/%s',IMGDIR,$this->icon) : '';
930	}
931
932	/**
933	 * Return the template description
934	 *
935	 * @return string Description
936	 */
937	public function getDescription() {
938		return $this->description;
939	}
940
941	/**
942	 * Set a template as invalid
943	 *
944	 * @param string Message indicating the reason the template has been invalidated
945	 */
946	public function setInvalid($msg,$admin=false) {
947		$this->invalid = true;
948		$this->invalid_reason = $msg;
949		$this->invalid_admin = $admin;
950	}
951
952	/**
953	 * Get the template validity or the reason it is invalid
954	 *
955	 * @return string Invalid reason, or false if not invalid
956	 */
957	public function isInValid() {
958		if ($this->invalid)
959			return $this->invalid_reason;
960		else
961			return false;
962	}
963
964	public function isAdminDisabled() {
965		return $this->invalid_admin;
966	}
967
968	/**
969	 * Set the minimum number of values for an attribute
970	 *
971	 * @param object Attribute
972	 * @param int
973	 */
974	private function setMinValueCount($attr,$value) {
975		$attribute = $this->getAttribute($attr);
976
977		if (! is_null($attribute))
978			$attribute->setMinValueCount($value);
979	}
980
981	/**
982	 * Set the LDAP type property for an attribute
983	 *
984	 * @param object Attribute
985	 * @param string (MUST,MAY,OPTIONAL)
986	 */
987	private function setAttrLDAPtype($attr,$value) {
988		$attribute = $this->getAttribute($attr);
989
990		if (is_null($attribute))
991			$attribute = $this->addAttribute($attr,array('values'=>array()));
992
993		$attribute->setLDAPtype($value);
994	}
995
996	/**
997	 * OnChangeAdd javascript processing
998	 */
999	public function OnChangeAdd($origin,$value) {
1000		$attribute = $this->getAttribute($origin);
1001
1002		if (preg_match('/^=(\w+)\((.*)\)$/',$value,$matches)) {
1003			$command = $matches[1];
1004			$arg = $matches[2];
1005		} else
1006			return;
1007
1008		switch ($command) {
1009			/*
1010			autoFill:string
1011			string is a literal string, and may contain many fields like %attr|start-end/flags%
1012				to substitute values read from other fields.
1013			|start-end is optional, but must be present if the k flag is used.
1014			/flags is optional.
1015
1016			flags may be:
1017			T:	Read display text from selection item (drop-down list), otherwise, read the value of the field
1018				For fields that aren't selection items, /T shouldn't be used, and the field value will always be read.
1019			k:	Tokenize:
1020				If the "k" flag is not given:
1021					A |start-end instruction will perform a sub-string operation upon
1022					the value of the attr, passing character positions start-end through.
1023					start can be 0 for first character, or any other integer.
1024					end can be 0 for last character, or any other integer for a specific position.
1025				If the "k" flag is given:
1026					The string read will be split into fields, using : as a delimiter
1027					"start" indicates which field number to pass through.
1028			K:	The string read will be split into fields, using ' ' as a delimiter "start" indicates which field number to pass through.
1029			l:	Make the result lower case.
1030			U:	Make the result upper case.
1031			*/
1032			case 'autoFill':
1033				if (! preg_match('/;/',$arg)) {
1034					system_message(array(
1035						'title'=>('Problem with autoFill() in template'),
1036						'body'=>sprintf('%s (<b>%s</b>)',('There is only 1 argument, when there should be two'),$attribute->getName(false)),
1037						'type'=>'warn'));
1038
1039					return;
1040				}
1041
1042				list($attr,$string) = preg_split('(([^,]+);(.*))',$arg,-1,PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
1043				preg_match_all('/%(\w+)(\|[0-9]*-[0-9]*)?(\/[KklTUA]+)?%/U',$string,$matchall);
1044				//print"<PRE>";print_r($matchall); //0 = highlevel match, 1 = attr, 2 = subst, 3 = mod
1045
1046				if (! isset($attribute->js['autoFill']))
1047					$attribute->js['autoFill'] = '';
1048
1049				$formula = $string;
1050				$formula = preg_replace('/^([^%])/','\'$1',$formula);
1051				$formula = preg_replace('/([^%])$/','$1\'',$formula);
1052
1053				# Check that our attributes match our schema attributes.
1054				foreach ($matchall[1] as $index => $checkattr) {
1055					$sattr = $this->getServer()->getSchemaAttribute($checkattr);
1056
1057					# If the attribute is the same as in the XML file, then dont need to do anything.
1058					if (! $sattr || ! strcasecmp($sattr->getName(),$checkattr))
1059						continue;
1060
1061					$formula = preg_replace("/$checkattr/",$sattr->getName(),$formula);
1062					$matchall[1][$index] = $sattr->getName();
1063				}
1064
1065				$elem_id = 0;
1066
1067				foreach ($matchall[0] as $index => $null) {
1068					$match_attr = strtolower($matchall[1][$index]);
1069					$match_subst = $matchall[2][$index];
1070					$match_mod = $matchall[3][$index];
1071
1072					$substrarray = array();
1073
1074					if (! isset($varcount[$match_attr]))
1075						$varcount[$match_attr] = 0;
1076					else
1077						$varcount[$match_attr]++;
1078
1079					$js_match_attr = $match_attr;
1080					$match_attr = $js_match_attr.'xx'.$varcount[$match_attr];
1081
1082					$formula = preg_replace('/%'.$js_match_attr.'([|\/%])/i','%'.$match_attr.'$1',$formula,1);
1083
1084					$attribute->js['autoFill'] .= sprintf("  var %s;\n",$match_attr);
1085					$attribute->js['autoFill'] .= sprintf(
1086							"  var elem$elem_id = document.getElementById(pre+'%s'+suf);\n".
1087							"  if (!elem$elem_id) return;\n", $js_match_attr);
1088
1089					if (strstr($match_mod,'T')) {
1090						$attribute->js['autoFill'] .= sprintf("  %s = elem$elem_id.options[elem$elem_id.selectedIndex].text;\n",
1091							$match_attr);
1092					} else {
1093						$attribute->js['autoFill'] .= sprintf("  %s = elem$elem_id.value;\n",$match_attr);
1094					}
1095
1096					$elem_id++;
1097
1098					if (strstr($match_mod,'k')) {
1099						preg_match_all('/([0-9]+)/',trim($match_subst),$substrarray);
1100						if (isset($substrarray[1][0])) {
1101							$tok_idx = $substrarray[1][0];
1102						} else {
1103							$tok_idx = '0';
1104						}
1105						$attribute->js['autoFill'] .= sprintf("   %s = %s.split(':')[%s];\n",$match_attr,$match_attr,$tok_idx);
1106
1107					} elseif (strstr($match_mod,'K')) {
1108						preg_match_all('/([0-9]+)/',trim($match_subst),$substrarray);
1109						if (isset($substrarray[1][0])) {
1110							$tok_idx = $substrarray[1][0];
1111						} else {
1112							$tok_idx = '0';
1113						}
1114						$attribute->js['autoFill'] .= sprintf("   %s = %s.split(' ')[%s];\n",$match_attr,$match_attr,$tok_idx);
1115
1116					} else {
1117						preg_match_all('/([0-9]*)-([0-9]*)/',trim($match_subst),$substrarray);
1118						if ((isset($substrarray[1][0]) && $substrarray[1][0]) || (isset($substrarray[2][0]) && $substrarray[2][0])) {
1119							$attribute->js['autoFill'] .= sprintf("   %s = %s.substr(%s,%s);\n",
1120								$match_attr,$match_attr,
1121								$substrarray[1][0] ? $substrarray[1][0] : '0',
1122								$substrarray[2][0] ? $substrarray[2][0] : sprintf('%s.length',$match_attr));
1123						}
1124					}
1125
1126					if (strstr($match_mod,'l')) {
1127						$attribute->js['autoFill'] .= sprintf("   %s = %s.toLowerCase();\n",$match_attr,$match_attr);
1128					}
1129					if (strstr($match_mod,'U')) {
1130						$attribute->js['autoFill'] .= sprintf("   %s = %s.toUpperCase();\n",$match_attr,$match_attr);
1131					}
1132					if (strstr($match_mod,'A')) {
1133						$attribute->js['autoFill'] .= sprintf("   %s = toAscii(%s);\n",$match_attr,$match_attr);
1134					}
1135
1136					# Matchfor only entry without modifiers.
1137					$formula = preg_replace('/^%('.$match_attr.')%$/U','$1 + \'\'',$formula);
1138					# Matchfor only entry with modifiers.
1139					$formula = preg_replace('/^%('.$match_attr.')(\|[0-9]*-[0-9]*)?(\/[KklTUA]+)?%$/U','$1 + \'\'',$formula);
1140					# Matchfor begining entry.
1141					$formula = preg_replace('/^%('.$match_attr.')(\|[0-9]*-[0-9]*)?(\/[KklTUA]+)?%/U','$1 + \'',$formula);
1142					# Matchfor ending entry.
1143					$formula = preg_replace('/%('.$match_attr.')(\|[0-9]*-[0-9]*)?(\/[KklTUA]+)?%$/U','\' + $1 ',$formula);
1144					# Match for entries not at begin/end.
1145					$formula = preg_replace('/%('.$match_attr.')(\|[0-9]*-[0-9]*)?(\/[:lTUA]+)?%/U','\' + $1 + \'',$formula);
1146					$attribute->js['autoFill'] .= "\n";
1147				}
1148
1149				$attribute->js['autoFill'] .= sprintf(" fillRec(pre+'%s'+suf, %s); // %s\n",strtolower($attr),$formula,$string);
1150				$attribute->js['autoFill'] .= "\n";
1151				break;
1152
1153			default: $return = '';
1154		}
1155	}
1156
1157	/**
1158	 * This functions main purpose is to discover our MUST attributes based on objectclass
1159	 * definitions in the template file and to discover which of the objectclasses are
1160	 * STRUCTURAL - without one, creating an entry will just product an LDAP error.
1161	 */
1162	private function rebuildTemplateAttrs() {
1163		$server = $this->getServer();
1164
1165		# Collect our structural, MUST & MAY attributes.
1166		$oclass_processed = array();
1167		$superclasslist = array();
1168		$allattrs = array('objectclass');
1169
1170		foreach ($this->getObjectClasses() as $oclass) {
1171			# If we get some superclasses - then we'll need to go through them too.
1172			$supclass = true;
1173			$inherited = false;
1174
1175			while ($supclass) {
1176				$soc = $server->getSchemaObjectClass($oclass);
1177
1178				if ($soc->getType() == 'structural' && (! $inherited))
1179					array_push($this->structural_oclass,$oclass);
1180
1181				# Make sure our MUST attributes are marked as such for this template.
1182				if ($soc->getMustAttrs())
1183					foreach ($soc->getMustAttrs() as $index => $details) {
1184						$objectclassattr = $details->getName();
1185
1186						# We add the 'objectClass' attribute, only if it's explicitly in the template attribute list
1187						if ((strcasecmp('objectClass',$objectclassattr) != 0) ||
1188								((strcasecmp('objectClass',$objectclassattr) == 0) && (! is_null($this->getAttribute($objectclassattr))))) {
1189
1190							# Go through the aliases, and ignore any that are already defined.
1191							$ignore = false;
1192							$sattr = $server->getSchemaAttribute($objectclassattr);
1193							foreach ($sattr->getAliases() as $alias) {
1194								if ($this->isAttrType($alias,'must')) {
1195									$ignore = true;
1196									break;
1197								}
1198							}
1199
1200							if ($ignore)
1201								continue;
1202
1203							$this->setAttrLDAPtype($sattr->getName(),'must');
1204							$this->setMinValueCount($sattr->getName(),1);
1205
1206							# We need to mark the attributes as show, except for the objectclass attribute.
1207							if (strcasecmp('objectClass',$objectclassattr) != 0) {
1208								$attribute = $this->getAttribute($sattr->getName());
1209								$attribute->show();
1210							}
1211						}
1212
1213						if (! in_array($objectclassattr,$allattrs))
1214							array_push($allattrs,$objectclassattr);
1215					}
1216
1217				if ($soc->getMayAttrs())
1218					foreach ($soc->getMayAttrs() as $index => $details) {
1219						$objectclassattr = $details->getName();
1220						$sattr = $server->getSchemaAttribute($objectclassattr);
1221
1222						# If it is a MUST attribute, skip to the next one.
1223						if ($this->isAttrType($objectclassattr,'must'))
1224							continue;
1225
1226						if (! $this->isAttrType($objectclassattr,'may'))
1227							$this->setAttrLDAPtype($sattr->getName(false),'optional');
1228
1229						if (! in_array($objectclassattr,$allattrs))
1230							array_push($allattrs,$objectclassattr);
1231					}
1232
1233				# Keep a list to objectclasses we have processed, so we dont get into a loop.
1234				array_push($oclass_processed,$oclass);
1235				$supoclasses = $soc->getSupClasses();
1236
1237				if (count($supoclasses) || count($superclasslist)) {
1238					foreach ($supoclasses as $supoclass) {
1239						if (! in_array($supoclass,$oclass_processed))
1240							$superclasslist[] = $supoclass;
1241					}
1242
1243					$oclass = array_shift($superclasslist);
1244					if ($oclass)
1245						$inherited = true;
1246					else
1247						$supclass = false;
1248
1249				} else {
1250					$supclass = false;
1251				}
1252			}
1253		}
1254
1255		# Check that attributes are defined by an ObjectClass
1256		foreach ($this->getAttributes(true) as $index => $attribute)
1257			if (! in_array($attribute->getName(),$allattrs) && (! array_intersect($attribute->getAliases(),$allattrs))
1258				&& (! in_array_ignore_case('extensibleobject',$this->getObjectClasses()))
1259				&& (! in_array_ignore_case($attribute->getName(),$server->getValue('server','custom_attrs')))) {
1260				unset($this->attributes[$index]);
1261
1262				if (! $_SESSION[APPCONFIG]->getValue('appearance','hide_template_warning'))
1263					system_message(array(
1264						'title'=>('Automatically removed attribute from template'),
1265						'body'=>sprintf('%s: <b>%s</b> %s',$this->getTitle(),$attribute->getName(false),('removed from template as it is not defined by an ObjectClass')),
1266						'type'=>'warn'));
1267			}
1268	}
1269
1270	/**
1271	 * Return an array, that can be passed to ldap_add().
1272	 * Attributes with empty values will be excluded.
1273	 */
1274	public function getLDAPadd($attrsOnly=false) {
1275		$return = array();
1276		$returnattrs = array();
1277
1278		if ($attrsOnly && count($returnattrs))
1279			return $returnattrs;
1280
1281		foreach ($this->getAttributes(true) as $attribute)
1282			if (! $attribute->isInternal() && count($attribute->getValues())) {
1283				$return[$attribute->getName()] = $attribute->getValues();
1284				$returnattrs[$attribute->getName()] = $attribute;
1285			}
1286
1287		# Ensure that our objectclasses has "top".
1288		if (isset($return['objectclass']) && ! in_array('top',$return['objectclass']))
1289			array_push($return['objectclass'],'top');
1290
1291		if ($attrsOnly)
1292			return $returnattrs;
1293
1294		return $return;
1295	}
1296
1297	/**
1298	 * Return an array, that can be passed to ldap_mod_replace().
1299	 * Only attributes that have changed their value will be returned.
1300	 *
1301	 * This function will cache its results, so that it can be called with count() to see
1302	 * if there are changes, and if they are, the 2nd call will just return the results
1303	 *
1304	 * @param boolean Return the attribute objects (useful for a confirmation process), or the modification array for ldap_modify()
1305	 */
1306	public function getLDAPmodify($attrsOnly=false,$index=0) {
1307		static $return = array();
1308		static $returnattrs = array();
1309
1310		if ($attrsOnly && isset($returnattrs[$index]) && count($returnattrs[$index]))
1311			return $returnattrs[$index];
1312
1313		$returnattrs[$index] = array();
1314		$return[$index] = array();
1315
1316		# If an objectclass is being modified, we need to remove all the orphan attributes that would result.
1317		if ($this->getAttribute('objectclass')->hasBeenModified()) {
1318			$attr_to_keep = array();
1319			$server = $this->getServer();
1320
1321			# Make sure that there will be a structural object class remaining.
1322			$haveStructural = false;
1323			foreach ($this->getAttribute('objectclass')->getValues() as $value) {
1324				$soc = $server->getSchemaObjectClass($value);
1325
1326				if ($soc) {
1327					if ($soc->isStructural())
1328						$haveStructural = true;
1329
1330					# While we are looping, workout which attributes these objectclasses define.
1331					foreach ($soc->getMustAttrs(true) as $value)
1332						if (! in_array($value->getName(),$attr_to_keep))
1333							array_push($attr_to_keep,$value->getName());
1334
1335					foreach ($soc->getMayAttrs(true) as $value)
1336						if (! in_array($value->getName(),$attr_to_keep))
1337							array_push($attr_to_keep,$value->getName());
1338				}
1339			}
1340
1341			if (! $haveStructural)
1342				error(('An entry should have one structural objectClass.'),'error','index.php');
1343
1344			# Work out the attributes to delete.
1345			foreach ($this->getAttribute('objectclass')->getRemovedValues() as $value) {
1346				$soc = $server->getSchemaObjectClass($value);
1347
1348				foreach ($soc->getMustAttrs() as $value) {
1349					$attribute = $this->getAttribute($value->getName());
1350
1351					if ($attribute && (! in_array($value->getName(),$attr_to_keep)) && ($value->getName() != 'objectclass'))
1352						#array_push($attr_to_delete,$value->getName(false));
1353						$attribute->setForceDelete();
1354				}
1355
1356				foreach ($soc->getMayAttrs() as $value) {
1357					$attribute = $this->getAttribute($value->getName());
1358
1359					if ($attribute && (! in_array($value->getName(),$attr_to_keep)) && ($value->getName() != 'objectclass'))
1360						$attribute->setForceDelete();
1361				}
1362			}
1363		}
1364
1365		foreach ($this->getAttributes(true) as $attribute)
1366			if ($attribute->hasBeenModified()
1367				&& (count(array_diff($attribute->getValues(),$attribute->getOldValues())) || ! count($attribute->getValues())
1368					|| $attribute->isForceDelete() || (count($attribute->getValues()) != count($attribute->getOldValues()))))
1369				$returnattrs[$index][$attribute->getName()] = $attribute;
1370
1371		if ($attrsOnly)
1372			return $returnattrs[$index];
1373
1374		foreach ($returnattrs[$index] as $attribute)
1375			$return[$index][$attribute->getName()] = $attribute->getValues();
1376
1377		return $return[$index];
1378	}
1379
1380	/**
1381	 * Get the attributes that are marked as force delete
1382	 * We'll cache this result in the event of multiple calls.
1383	 */
1384	public function getForceDeleteAttrs() {
1385		static $result = array();
1386
1387		if (count($result))
1388			return $result;
1389
1390		foreach ($this->attributes as $attribute)
1391			if ($attribute->isForceDelete())
1392				array_push($result,$attribute);
1393
1394		return $result;
1395	}
1396
1397	/**
1398	 * Get available attributes
1399	 */
1400	public function getAvailAttrs() {
1401		$attributes = array();
1402		$server = $this->getServer();
1403
1404		# Initialise the Attribute Factory.
1405		$attribute_factory = new AttributeFactory();
1406
1407		if (in_array_ignore_case('extensibleobject',$this->getObjectClasses())) {
1408			foreach ($server->SchemaAttributes() as $sattr) {
1409				$attribute = $attribute_factory->newAttribute($sattr->getName(),array('values'=>array()),$server->getIndex(),null);
1410				array_push($attributes,$attribute);
1411			}
1412
1413		} else {
1414			$attrs = array();
1415
1416			foreach ($this->getObjectClasses() as $oc) {
1417				$soc = $server->getSchemaObjectClass($oc);
1418				$attrs = array_merge($attrs,$soc->getMustAttrNames(true),$soc->getMayAttrNames(true));
1419				$attrs = array_unique($attrs);
1420			}
1421
1422			foreach ($attrs as $attr)
1423				if (is_null($this->getAttribute($attr))) {
1424					$attribute = $attribute_factory->newAttribute($attr,array('values'=>array()),$server->getIndex(),null);
1425					array_push($attributes,$attribute);
1426				}
1427		}
1428
1429		masort($attributes,'name');
1430		return $attributes;
1431	}
1432
1433	public function isNoLeaf() {
1434		return $this->noleaf;
1435	}
1436}
1437?>
1438