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