Cell width: |  allows an attribute "width" to set the cell width (e.g.  |  or  | ).
	 * Line breaks: Line breaks can be specified by adding a < > tag. The new line will start at the left border of the PDF document.
	 * 
	 *  
	 * Examples: 
	 *  
	 * Simple name+value lines:
  
	 * In most cases you will just want to display a single line per attribute with its name and value. 
	 *  
	 * 'myAttribute' => 'AttrName12345' 
	 *  
	 * This will give the following PDF output: 
	 *  
	 * Attribute name: 12345 
	 *  
	 *  
	 * Multiline values:
  
	 * Sometimes you have multivalued attributes where it is not applicable to write all values in one line but
	 * where you want to list your values one below the other or show a table. This can be done by using the  |  tag. 
	 *  
	 * This example only uses one column but you can just use more  |  tags per  tag to display more columns. 
	 *  
	 * 'myAttribute' => 'AttrName| 123 |  | 456 |  | 789 |  '
	 *
	 * @param string $typeId type id (user, group, host)
	 * @return array PDF entries
	 *
	 * @see baseModule::get_metaData()
	 */
	public function get_pdfFields($typeId) {
		return ((isset($this->meta['PDF_fields'])) ? $this->meta['PDF_fields'] : array());
	}
	/**
	* Returns the PDF entries for this module.
	*
	* @param array $pdfKeys list of PDF keys that are included in document
	* @param string $typeId type id (user, group, host)
	* @return PDFEntry[] list of key => PDFEntry
	*/
	public function get_pdfEntries($pdfKeys, $typeId) {
		return array();
	}
	/**
	 * Adds a simple PDF entry to the given array.
	 *
	 * @param array $result result array (entry will be added here)
	 * @param String $name ID
	 * @param String $label label name
	 * @param String $attrName attribute name (default: =$name)
	 * @param String $delimiter delimiter if multiple attribute values exist (default: ", ")
	 */
	protected function addSimplePDFField(&$result, $name, $label, $attrName = null, $delimiter = ', ') {
		if ($attrName == null) {
			$attrName = $name;
		}
		$value = '';
		if (isset($this->attributes[$attrName]) && (sizeof($this->attributes[$attrName]) > 0)) {
			natcasesort($this->attributes[$attrName]);
			$value = implode($delimiter, $this->attributes[$attrName]);
			$value = trim($value);
		}
		$result[get_class($this) . '_' . $name][] = new PDFLabelValue($label, $value);
	}
	/**
	 * Adds a simple PDF entry with the given key and value.
	 *
	 * @param array $result result array (entry will be added here)
	 * @param String $name ID
	 * @param String $label label name
	 * @param mixed $value value as String or array
	 * @param String $delimiter delimiter if value is array (default: ", ")
	 */
	public function addPDFKeyValue(&$result, $name, $label, $value, $delimiter = ', ') {
		if (is_array($value)) {
			natcasesort($value);
			$value = implode($delimiter, $value);
		}
		$result[get_class($this) . '_' . $name][] = new PDFLabelValue($label, $value);
	}
	/**
	 * Adds a table entry to the PDF.
	 *
	 * @param array $result result array (entry will be added here)
	 * @param String $name ID
	 * @param PDFTable $table table
	 */
	public function addPDFTable(&$result, $name, $table) {
		if (empty($table->rows)) {
			return;
		}
		$result[get_class($this) . '_' . $name][] = $table;
	}
	/**
	* Returns an array containing all input columns for the file upload.
	*
	* Calling this method does not require the existence of an enclosing {@link accountContainer}. 
	*  
	* This funtion returns an array which contains subarrays which represent an upload column.
	* Syntax of column arrays:
	*  
	*   array(
	*    string: name,  // fixed non-translated name which is used as column name (should be of format: _)
	*    string: description,  // short descriptive name
	*    string: help,  // help ID
	*    string: example,  // example value
	*    string: values, // possible input values (optional)
	*    string: default, // default value (optional)
	*    boolean: required  // true, if user must set a value for this column
	*    boolean: unique // true if all values of this column must be different values (optional, default: "false")
	*   )
	*
	* @param array $selectedModules list of selected account modules
	* @return array column list
	*
	* @see baseModule::get_metaData()
	*/
	public function get_uploadColumns($selectedModules) {
		if (isset($this->meta['upload_columns'])) return $this->meta['upload_columns'];
		else return array();
	}
	/**
	* Returns a list of module names which must be processed in building the account befor this module.
	*
	* Calling this method does not require the existence of an enclosing {@link accountContainer}. 
	*  
	* The named modules may not be active, LAM will check this automatically.
	*
	* @return array list of module names
	*
	* @see baseModule::get_metaData()
	*/
	public function get_uploadPreDepends() {
		if (isset($this->meta['upload_preDepends'])) return $this->meta['upload_preDepends'];
		else return array();
	}
	/**
	* In this function the LDAP accounts are built.
	*
	* Calling this method does not require the existence of an enclosing {@link accountContainer}. 
	*  
	* Returns an array which contains subarrays to generate StatusMessages if any errors occured.
	*
	* @param array $rawAccounts the user input data, contains one subarray for each account.
	* @param array $ids list of IDs for column position (e.g. "posixAccount_uid" => 5)
	* @param array $partialAccounts list of hash arrays (name => value) which are later added to LDAP
	* @param array $selectedModules list of selected account modules
	* @return array list of error messages if any
	*/
	public function build_uploadAccounts($rawAccounts, $ids, &$partialAccounts, $selectedModules) {
		// must be implemented in sub modules
		return array();
	}
	/**
	* Maps simple upload fields directly to LDAP attribute values.
	*
	* @param array $rawAccounts the user input data, contains one subarray for each account.
	* @param array $ids list of IDs for column position (e.g. "posixAccount_uid" => 5)
	* @param array $partialAccounts list of hash arrays (name => value) which are later added to LDAP
	* @param String $position current position in CSV
	* @param String $colName column name
	* @param String $attrName LDAP attribute name
	* @param String $regexID for get_preg() (e.g. 'ascii')
	* @param array $message error message to add if regex does not match
	* @param array $errors list of error messages if any
	* @param String $regexSplit multiple values are separated and can be split with this preg_split expression (e.g. "/;[ ]?/")
	*/
	protected function mapSimpleUploadField(&$rawAccounts, &$ids, &$partialAccounts, $position, $colName, $attrName, $regex = null, $message = array(), &$errors = array(), $regexSplit = null) {
		if (!isset($ids[$colName])) {
			return;
		}
		if (!empty($rawAccounts[$position][$ids[$colName]])) {
			// single value
			if ($regexSplit == null) {
				if (!empty($regex) && !get_preg($rawAccounts[$position][$ids[$colName]], $regex)) {
					$errMsg = $message;
					array_push($errMsg, array($position));
					$errors[] = $errMsg;
				}
				$partialAccounts[$position][$attrName] = trim($rawAccounts[$position][$ids[$colName]]);
			}
			// multi-value
			else {
				$list = preg_split($regexSplit, trim($rawAccounts[$position][$ids[$colName]]));
				$partialAccounts[$position][$attrName] = $list;
				if (!empty($regex)) {
					for ($x = 0; $x < sizeof($list); $x++) {
						if (!get_preg($list[$x], $regex)) {
							$errMsg = $message;
							array_push($errMsg, array($position));
							$errors[] = $errMsg;
							break;
						}
					}
				}
			}
		}
	}
	/**
	 * This function returns the help entry array for a specific help id.
	 *
	 * Calling this method does not require the existence of an enclosing {@link accountContainer}. 
	 *  
	 * The result is an hashtable with the following keys: 
	 * 
	 * - Headline (required)
 
	 * The headline of this help entry. Can consist of any alpha-numeric characters. No HTML/CSS elements are allowed. 
	 * - Text (required)
 
	 * The text of the help entry which may contain any alpha-numeric characters. 
	 * - SeeAlso (optional)
 
	 * A reference to anonther related web site. It must be an array containing a field called "text" with the link text
	 * that should be displayed and a field called "link" which is the link target. 
	 *  
	 *  
	 * Example: 
	 *  
	 * array('Headline' => 'This is the head line', 'Text' => 'Help content', 'SeeAlso' => array('text' => 'LAM homepage', 'link' => 'http://www.ldap-account-manager.org/'))
	 *
	 * @param string $id The id string for the help entry needed.
	 * @return array The desired help entry.
	 *
	 * @see baseModule::get_metaData()
	 */
	public function get_help($id) {
		if(isset($this->meta['help'][$id])) {
			return $this->meta['help'][$id];
		}
		elseif(isset($this->meta['help'][$this->scope][$id])) {
			return $this->meta['help'][$this->scope][$id];
		}
		else {
			return false;
		}
	}
	/**
	* This function is used to check if this module page can be displayed.
	*
	* Calling this method requires the existence of an enclosing {@link accountContainer}. 
	*  
	* Your module might depend on input of other modules. This function determines if the user
	* can change to your module page or not. The return value is true if your module accepts
	* input, otherwise false. 
	* This method's return value defaults to true.
	*
	* @return boolean true, if page can be displayed
	*/
	public function module_ready() {
		return true;
	}
	/**
	* This function is used to check if all settings for this module have been made.
	*
	* Calling this method requires the existence of an enclosing {@link accountContainer}. 
	*  
	* This function tells LAM if it can create/modify the LDAP account. If your module needs any
	* additional input then set this to false. The user will be notified that your module needs
	* more input. 
	* This method's return value defaults to true.
	*
	* @return boolean true, if settings are complete
	*/
	public function module_complete() {
		return true;
	}
	/**
	* Controls if the module button the account page is visible and activated.
	*
	* Calling this method requires the existence of an enclosing {@link accountContainer}. 
	*  
	* Possible return values:
	* 
	* - enabled: button is visible and active
 
	* - disabled: button is visible and deactivated (greyed)
 
	* - hidden: no button will be shown
 
	*  
	*
	* @return string status ("enabled", "disabled", "hidden")
	*/
	public function getButtonStatus() {
		return "enabled";
	}
	/**
	 * Runs any actions that need to be done before an LDAP entry is created.
	 *
	 * @param array $attributes LDAP attributes of this entry (attributes are provided as reference, handle modifications of $attributes with care)
	 * @return array array which contains status messages. Each entry is an array containing the status message parameters.
	 */
	public function doUploadPreActions($attributes) {
		return array();
	}
	/**
	* This function is responsible to do additional tasks after the account has been created in LDAP (e.g. modifying group memberships, adding Quota etc..).
	*
	* Calling this method does not require the existence of an enclosing {@link accountContainer}. 
	*  
	* This function is called as long as the returned status is 'finished'. Please make sure
	* that one function call lasts no longer than 3-4 seconds. Otherwise the upload may fail
	* because the time limit is exceeded. You should not make more than one LDAP operation in
	* each call.
	*
	* @param array $data array containing one account in each element
	* @param array $ids maps the column names to keys for the sub arrays (array( => ))
	* @param array $failed list of account numbers which could not be successfully uploaded to LDAP
	* @param array $temp variable to store temporary data between two post actions
	* @param array $accounts list of LDAP entries
	* @return array current status
	*   array (
	*    'status' => 'finished' | 'inProgress' // defines if all operations are complete
	*    'progress' => 0..100 // the progress of the operations in percent
	*    'errors' => array // list of arrays which are used to generate StatusMessages
	*   )
	*/
	public function doUploadPostActions(&$data, $ids, $failed, &$temp, &$accounts) {
		return array(
			'status' => 'finished',
			'progress' => 100,
			'errors' => array()
		);
	}
	/**
	* Returns a list of modifications which have to be made to the LDAP account.
	*
	* Calling this method requires the existence of an enclosing {@link accountContainer}. 
	*  
	*
	*  This function returns an array with 3 entries:
	*  array( DN1 ('add' => array($attr), 'remove' => array($attr), 'modify' => array($attr)), DN2 .... )
	*  DN is the DN to change. It is possible to change several DNs (e.g. create a new user and add him
	* to some groups via attribute memberUid) 
	*  "add" are attributes which have to be added to the LDAP entry
	*  "remove" are attributes which have to be removed from the LDAP entry
	*  "modify" are attributes which have to be modified in the LDAP entry
	*  "notchanged" are attributes which stay unchanged
	*  "info" values with informational value (e.g. to be used later by pre/postModify actions)
	*  
	*  This builds the required comands from $this-attributes and $this->orig.
	*
	* @return array list of modifications
	*/
	public function save_attributes() {
		return $this->getAccountContainer()->save_module_attributes($this->attributes, $this->orig);
	}
	/**
	 * Allows the module to run commands before the LDAP entry is changed or created.
	 *
	 * Calling this method requires the existence of an enclosing {@link accountContainer}. 
	 *  
	 * The modification is aborted if an error message is returned.
	 *
	 * @param boolean $newAccount new account
	 * @param array $attributes LDAP attributes of this entry (added/modified attributes are provided as reference, handle modifications of $attributes with care)
	 * @return array array which contains status messages. Each entry is an array containing the status message parameters.
	 */
	public function preModifyActions($newAccount, $attributes) {
		return array();
	}
	/**
	 * Allows the module to run commands after the LDAP entry is changed or created.
	 *
	 * Calling this method requires the existence of an enclosing {@link accountContainer}.
	 *
	 * @param boolean $newAccount new account
	 * @param array $attributes LDAP attributes of this entry
	 * @return array array which contains status messages. Each entry is an array containing the status message parameters.
	 */
	public function postModifyActions($newAccount, $attributes) {
		return array();
	}
	/**
	 * Allows the module to run commands before the LDAP entry is deleted.
	 *
	 * Calling this method requires the existence of an enclosing {@link accountContainer}. 
	 *
	 * @return array Array which contains status messages. Each entry is an array containing the status message parameters.
	 */
	public function preDeleteActions() {
		return array();
	}
	/**
	 * Allows the module to run commands after the LDAP entry is deleted.
	 *
	 * Calling this method requires the existence of an enclosing {@link accountContainer}.
	 *
	 * @return array Array which contains status messages. Each entry is an array containing the status message parameters.
	 */
	public function postDeleteActions() {
		return array();
	}
	/**
	* This function returns an array with the same syntax as save_attributes().
	*
	* Calling this method requires the existence of an enclosing {@link accountContainer}. 
	*  
	* It allows additional LDAP changes when an account is deleted.
	*
	* @return List of LDAP operations, same as for save_attributes()
	*/
	public function delete_attributes() {
		return 0;
	}
	/**
	* This function creates meta HTML code which will be displayed when an account should be deleted.
	*
	* Calling this method requires the existence of an enclosing {@link accountContainer}. 
	*  
	* This can be used to interact with the user, e.g. should the home directory be deleted? The output
	* of all modules is displayed on a single page.
	*
	* @return htmlElement meta HTML object
	* @see htmlElement
	*/
	public function display_html_delete() {
		return 0;
	}
	/**
	 * Defines if the LDAP entry has only virtual child entries. This is the case for e.g. LDAP views.
	 *
	 * @return boolean has only virtual children
	 */
	public function hasOnlyVirtualChildren() {
		return false;
	}
	/**
	 * This function processes user input.
	 *
	 * Calling this method requires the existence of an enclosing {@link accountContainer}. 
	 *  
	 * It checks the user input and saves changes in the module's data structures. 
	 *  
	 * Example: return array(array('ERROR', 'Invalid input!', 'This is not allowed here.'));
	 *
	 * @return array Array which contains status messages. Each entry is an array containing the status message parameters.
	 */
	public abstract function process_attributes();
	/**
	 * This function creates meta HTML code to display the module page.
	 *
	 * Calling this method requires the existence of an enclosing {@link accountContainer}.
	 *
	 * @return htmlElement meta HTML object
	 *
	 * @see htmlElement
	 */
	public abstract function display_html_attributes();
	/**
	 * Adds a simple text input field to the given htmlTable.
	 * The field name will be the same as the attribute name. There must also be a help entry with the attribute name as ID.
	 * A new line will also be added after this entry so multiple calls will show the fields one below the other.
	 *
	 * @param htmlTable $container parent container
	 * @param String $attrName attribute name
	 * @param String $label label name
	 * @param boolean $required this is a required field (default false)
	 * @param integer $length field length
	 * @param boolean $isTextArea show as text area (default false)
	 * @param array $autoCompleteValues values for auto-completion
	 * @return mixed reference to htmlTableExtendedInputField/htmlTableExtendedInputTextarea
	 */
	protected function &addSimpleInputTextField(&$container, $attrName, $label, $required = false, $length = null, $isTextArea = false, $autoCompleteValues = null) {
		$value = '';
		if (isset($this->attributes[$attrName][0])) {
			$value = $this->attributes[$attrName][0];
		}
		if ($isTextArea) {
			$cols = 30;
			if ($length != null) {
				$cols = $length;
			}
			$input = new htmlTableExtendedInputTextarea($attrName, $value, $cols, 3, $label, $attrName);
		}
		else {
			$input = new htmlTableExtendedInputField($label, $attrName, $value, $attrName);
			if ($length != null) {
				$input->setFieldSize($length);
			}
			if (!empty($autoCompleteValues)) {
				$input->enableAutocompletion($autoCompleteValues);
			}
		}
		$input->setRequired($required);
		$container->addElement($input, true);
		return $input;
	}
	/**
	 * Adds a simple read-only field to the given container.
	 *
	 * @param htmlTable $container parent container
	 * @param String $attrName attribute name
	 * @param String $label field label
	 */
	protected function addSimpleReadOnlyField(&$container, $attrName, $label) {
		$val = '';
		if (!empty($this->attributes[$attrName][0])) {
			$values = $this->attributes[$attrName];
			array_map('htmlspecialchars', $values);
			$val = implode(' ', $values);
		}
		$labelBox = new htmlOutputText($label);
		if (!empty($this->attributes[$attrName]) && (sizeof($this->attributes[$attrName]) > 1)) {
			$labelBox->alignment = htmlElement::ALIGN_TOP;
		}
		$container->addElement($labelBox);
		$container->addElement(new htmlOutputText($val, false), true);
	}
	/**
	 * Adds a text input field that may contain multiple values to the given htmlTable.
	 * The field name will be the same as the attribute name plus a counting number (e.g. street_0).
	 * The last field will be followed by a button to add a new value. This is named add_{attribute name} (e.g. add_street).
	 * There must be a help entry with the attribute name as ID.
	 * A new line will also be added after this entry so multiple calls will show the fields one below the other.
	 *
	 * @param htmlTable $container parent container
	 * @param String $attrName attribute name
	 * @param String $label label name
	 * @param boolean $required this is a required field (default false)
	 * @param integer $length field length
	 * @param boolean $isTextArea show as text area (default false)
	 * @param array $autoCompleteValues values for auto-completion
	 * @param integer $fieldSize field size
	 * @param array $htmlIDs reference to array where to add the generated HTML IDs of the input fields
	 */
	protected function addMultiValueInputTextField(&$container, $attrName, $label, $required = false, $length = null, $isTextArea = false,
				$autoCompleteValues = null, $fieldSize = null, &$htmlIDs = null) {
			$values = array();
			if (isset($this->attributes[$attrName][0])) {
				$values = $this->attributes[$attrName];
			}
			if (sizeof($values) == 0) {
				$values[] = '';
			}
			natcasesort($values);
			$values = array_values($values);
			if ($label !== null) {
				$labelTextOut = new htmlOutputText($label);
				$labelTextOut->alignment = htmlElement::ALIGN_TOP;
				$container->addElement($labelTextOut);
			}
			$subContainer = new htmlTable();
			$subContainer->alignment = htmlElement::ALIGN_TOP;
			for ($i = 0; $i < sizeof($values); $i++) {
				if (!$isTextArea) {
					$input = new htmlInputField($attrName . '_' . $i, $values[$i]);
					if (!empty($length)) {
						$input->setFieldMaxLength($length);
					}
					if (!empty($fieldSize)) {
						$input->setFieldSize($fieldSize);
					}
					if (!empty($autoCompleteValues)) {
						$input->enableAutocompletion($autoCompleteValues);
					}
					$subContainer->addElement($input);
				}
				else {
					$cols = 30;
					if ($length != null) {
						$cols = $length;
					}
					$subContainer->addElement(new htmlInputTextarea($attrName . '_' . $i, $values[$i], $cols, 3));
				}
				if (!empty($htmlIDs)) {
					$htmlIDs[] = $attrName . '_' . $i;
				}
				if (!empty($values[$i])) {
					$subContainer->addElement(new htmlButton('del_' . $attrName . '_' . $i, 'del.png', true));
				}
				if ($i == 0) {
					$subContainer->addElement(new htmlButton('add_' . $attrName, 'add.png', true));
				}
				$subContainer->addNewLine();
			}
			$container->addElement($subContainer);
			$help = new htmlHelpLink($attrName);
			$help->alignment = htmlElement::ALIGN_TOP;
			$container->addElement($help, true);
	}
	/**
	 * Validates a multi-value text field.
	 * The input fields must be created with function addMultiValueInputTextField().
	 * If validation is used then there must exist a message named [{attribute name}][0] (e.g. $this->messages['street'][0]).
	 *
	 * @param String $attrName attribute name
	 * @param array $errors errors array where to put validation errors
	 * @param String $validationID validation ID for function get_preg() (default: null, null means no validation)
	 */
	protected function processMultiValueInputTextField($attrName, &$errors, $validationID = null) {
		$counter = 0;
		while (isset($_POST[$attrName . '_' . $counter])) {
			$this->attributes[$attrName][$counter] = trim($_POST[$attrName . '_' . $counter]);
			if (($this->attributes[$attrName][$counter] == '') || isset($_POST['del_' . $attrName . '_' . $counter])) {
				unset($this->attributes[$attrName][$counter]);
			}
			elseif (($validationID != null) && ($this->attributes[$attrName][$counter] != '') && !get_preg($this->attributes[$attrName][$counter], $validationID)) {
				$msg = $this->messages[$attrName][0];
				if (sizeof($msg) < 3) {
					$msg[] = htmlspecialchars($this->attributes[$attrName][$counter]);
				}
				$errors[] = $msg;
			}
			$counter++;
		}
		if (isset($_POST['add_' . $attrName])) {
			$this->attributes[$attrName][] = '';
		}
		$this->attributes[$attrName] = array_values(array_unique($this->attributes[$attrName]));
	}
	/**
	 * Adds a select field type that may contain multiple values to the given htmlTable.
	 * The field name will be the same as the attribute name plus a counting number (e.g. street_0).
	 * The last field will be followed by a button to add a new value. This is named add_{attribute name} (e.g. add_street).
	 * There must be a help entry with the attribute name as ID.
	 * A new line will also be added after this entry so multiple calls will show the fields one below the other.
	 *
	 * @param htmlTable $container parent container
	 * @param String $attrName attribute name
	 * @param String $label label name
	 * @param array $options options for the selects
	 * @param boolean $hasDescriptiveOptions has descriptive options
	 * @param boolean $required this is a required field (default false)
	 * @param integer $fieldSize field size
	 * @param array $htmlIDs reference to array where to add the generated HTML IDs of the input fields
	 */
	protected function addMultiValueSelectField(&$container, $attrName, $label, $options, $hasDescriptiveOptions = false,
				$required = false, $fieldSize = 1, &$htmlIDs = null) {
			$values = array();
			if (isset($this->attributes[$attrName][0])) {
				$values = $this->attributes[$attrName];
			}
			if (sizeof($values) == 0) {
				$values[] = '';
			}
			natcasesort($values);
			$values = array_values($values);
			if ($label !== null) {
				$labelTextOut = new htmlOutputText($label);
				$labelTextOut->alignment = htmlElement::ALIGN_TOP;
				$container->addElement($labelTextOut);
			}
			$subContainer = new htmlTable();
			$subContainer->alignment = htmlElement::ALIGN_TOP;
			for ($i = 0; $i < sizeof($values); $i++) {
				$input = new htmlSelect($attrName . '_' . $i, $options, array($values[$i]), $fieldSize);
				$subContainer->addElement($input);
				if (!empty($htmlIDs)) {
					$htmlIDs[] = $attrName . '_' . $i;
				}
				if (!empty($values[$i])) {
					$subContainer->addElement(new htmlButton('del_' . $attrName . '_' . $i, 'del.png', true));
				}
				if ($i == 0) {
					$subContainer->addElement(new htmlButton('add_' . $attrName, 'add.png', true));
				}
				$subContainer->addNewLine();
			}
			$container->addElement($subContainer);
			$help = new htmlHelpLink($attrName);
			$help->alignment = htmlElement::ALIGN_TOP;
			$container->addElement($help, true);
	}
	/**
	 * Validates a multi-value select field.
	 * The select fields must be created with function addMultiValueSelectField().
	 *
	 * @param String $attrName attribute name
	 */
	protected function processMultiValueSelectField($attrName) {
		$counter = 0;
		while (isset($_POST[$attrName . '_' . $counter])) {
			$this->attributes[$attrName][$counter] = trim($_POST[$attrName . '_' . $counter]);
			if (($this->attributes[$attrName][$counter] == '') || isset($_POST['del_' . $attrName . '_' . $counter])) {
				unset($this->attributes[$attrName][$counter]);
			}
			$counter++;
		}
		if (isset($_POST['add_' . $attrName])) {
			$this->attributes[$attrName][] = '';
		}
		$this->attributes[$attrName] = array_values(array_unique($this->attributes[$attrName]));
	}
	/**
	 * Adds a simple text input field for the self service.
	 * The field name will be the same as the class name plus "_" plus attribute name (e.g. posixAccount_cn).
	 *
	 * @param array $container array that is used as return value for getSelfServiceOptions()
	 * @param String $name attribute name (== field name)
	 * @param String $label label to display in front of input field
	 * @param array $fields list of active fields
	 * @param array $attributes attributes of LDAP account
	 * @param array $readOnlyFields list of read-only fields
	 * @param boolean $required field is required
	 * @param boolean $isTextArea display as text area
	 */
	protected function addSimpleSelfServiceTextField(&$container, $name, $label, &$fields, &$attributes, &$readOnlyFields, $required = false, $isTextArea = false) {
		$value = '';
		if (isset($attributes[$name][0])) {
			$value = $attributes[$name][0];
		}
		if (!$isTextArea && !in_array($name, $readOnlyFields)) {
			$field = new htmlInputField(get_class($this) . '_' . $name, $value);
			$field->setRequired($required);
			$field->setFieldSize(null);
		}
		elseif ($isTextArea && !in_array($name, $readOnlyFields)) {
			$field = new htmlInputTextarea(get_class($this) . '_' . $name, $value, null, null);
		}
		else {
			if (!$isTextArea) {
				$field = new htmlOutputText($value);
			}
			else {
				$value = htmlspecialchars($value);
				$value = str_replace("\n", ' ', $value);
				$field = new htmlOutputText($value, false);
			}
		}
		$row = new htmlResponsiveRow();
		$row->addLabel(new htmlOutputText($this->getSelfServiceLabel($name, $label)));
		$row->addField($field);
		$container[$name] = $row;
	}
	/**
	 * Checks the input value of a self service text field.
	 * The field name must be the same as the class name plus "_" plus attribute name (e.g. posixAccount_cn).
	 * If validation is used then there must exist a message named [{attribute name}][0] (e.g. $this->messages['street'][0]).
	 *
	 * @param array $container return value of checkSelfServiceOptions()
	 * @param String $name attribute name
	 * @param array $attributes LDAP attributes
	 * @param string $fields input fields
	 * @param array $readOnlyFields list of read-only fields
	 * @param String $validationID validation ID for get_preg()
	 */
	protected function checkSimpleSelfServiceTextField(&$container, $name, &$attributes, $fields, &$readOnlyFields, $validationID = null) {
		if (in_array($name, $fields) && !in_array($name, $readOnlyFields)) {
			$fieldName = get_class($this) . '_' . $name;
			if (isset($_POST[$fieldName]) && ($_POST[$fieldName] != '')) {
				if (($validationID != null) && !get_preg($_POST[$fieldName], $validationID)) {
					$container['messages'][] = $this->messages[$name][0];
				}
				else {
					if (isset($attributes[$name]) && ($attributes[$name][0] != $_POST[$fieldName])) {
						$container['mod'][$name] = array($_POST[$fieldName]);
					}
					elseif (!isset($attributes[$name])) {
						$container['add'][$name] = array($_POST[$fieldName]);
					}
				}
			}
			elseif (isset($attributes[$name])) {
				$container['del'][$name] = $attributes[$name];
			}
		}
	}
	/**
	 * Returns a list of managed object classes for this module.
	 *
	 * Calling this method does not require the existence of an enclosing {@link accountContainer}. 
	 *  
	 * This is used to fix spelling errors in LDAP-Entries (e.g. if "posixACCOUNT" is read instead of "posixAccount" from LDAP). 
	 *  
	 * Example: return array('posixAccount')
	 *
	 * @return array list of object classes
	 *
	 * @see baseModule::get_metaData()
	 */
	public function getManagedObjectClasses() {
		if (isset($this->meta['objectClasses']) && is_array($this->meta['objectClasses'])) return $this->meta['objectClasses'];
		else return array();
	}
	/**
	 * Returns a list of aliases for LDAP attributes.
	 *
	 * Calling this method does not require the existence of an enclosing {@link accountContainer}. 
	 *  
	 * All alias attributes will be renamed to the given attribute names.
	 *
	 * @return array list of aliases like array("alias name" => "attribute name")
	 *
	 * @see baseModule::get_metaData()
	 */
	public function getLDAPAliases() {
		if (isset($this->meta['LDAPaliases']) && is_array($this->meta['LDAPaliases'])) return $this->meta['LDAPaliases'];
		else return array();
	}
	/**
	 * Returns a list of LDAP attributes which are managed by this module.
	 * All attribute names will be renamed to match the given spelling.
	 *
	 * @return array list of attributes
	 *
	 * @see baseModule::get_metaData()
	 */
	public function getManagedAttributes() {
		if (isset($this->meta['attributes']) && is_array($this->meta['attributes'])) return $this->meta['attributes'];
		else return array();
	}
	/**
	 * Returns a list of operational LDAP attributes which are managed by this module and need to be explicitly set for LDAP search.
	 *
	 * @return array list of hidden attributes
	 *
	 * @see baseModule::get_metaData()
	 */
	public function getManagedHiddenAttributes() {
		if (isset($this->meta['hiddenAttributes']) && is_array($this->meta['hiddenAttributes'])) return $this->meta['hiddenAttributes'];
		else return array();
	}
	/**
	 * This function returns a list of PHP extensions (e.g. hash) which are needed by this module.
	 *
	 * Calling this method does not require the existence of an enclosing {@link accountContainer}.
	 *
	 * @return array extensions
	 *
	 * @see baseModule::get_metaData()
	 */
	public function getRequiredExtensions() {
		if (isset($this->meta['extensions']) && is_array($this->meta['extensions'])) return $this->meta['extensions'];
		else return array();
	}
	/**
	 * This function returns a list of possible LDAP attributes (e.g. uid, cn, ...) which can be used to search for LDAP objects.
	 *
	 * Calling this method does not require the existence of an enclosing {@link accountContainer}.
	 *
	 * @return array attributes
	 *
	 * @see baseModule::get_metaData()
	 */
	public function getSelfServiceSearchAttributes() {
		if (isset($this->meta['selfServiceSearchAttributes']) && is_array($this->meta['selfServiceSearchAttributes'])) return $this->meta['selfServiceSearchAttributes'];
		else return array();
	}
	/**
	 * Returns a list of possible input fields and their descriptions.
	 *
	 * Calling this method does not require the existence of an enclosing {@link accountContainer}. 
	 *  
	 * Format: array( => )
	 *
	 * @return array fields
	 *
	 * @see baseModule::get_metaData()
	 */
	public function getSelfServiceFields() {
		if (isset($this->meta['selfServiceFieldSettings']) && is_array($this->meta['selfServiceFieldSettings'])) return $this->meta['selfServiceFieldSettings'];
		else return array();
	}
	/**
	 * Returns if a given self service field can be set in read-only mode.
	 *
	 * @param String $fieldID field identifier
	 * @param selfServiceProfile $profile currently edited profile
	 * @return boolean may be set read-only
	 */
	public function canSelfServiceFieldBeReadOnly($fieldID, $profile) {
		if (isset($this->meta['selfServiceReadOnlyFields']) && is_array($this->meta['selfServiceReadOnlyFields'])) {
			return in_array($fieldID, $this->meta['selfServiceReadOnlyFields']);
		}
		return false;
	}
	/**
	 * Returns if a self service field can be relabeled.
	 *
	 * @param String $fieldID field ID
	 * @param selfServiceProfile $profile currently edited profile
	 * @return boolean may be relabeled
	 */
	public function canSelfServiceFieldBeRelabeled($fieldID, $profile) {
		if (isset($this->meta['selfServiceNoRelabelFields']) && is_array($this->meta['selfServiceNoRelabelFields'])) {
			return !in_array($fieldID, $this->meta['selfServiceNoRelabelFields']);
		}
		return true;
	}
	/**
	 * Returns the field label. This can be either the given default label or an override value from profile.
	 *
	 * @param String $fieldID field ID
	 * @param String $defaultLabel default label text
	 * @return String label
	 */
	protected function getSelfServiceLabel($fieldID, $defaultLabel) {
		if (!$this->canSelfServiceFieldBeRelabeled($fieldID, $this->selfServiceSettings)) {
			return $defaultLabel;
		}
		$key = get_class($this) . '_' . $fieldID;
		return empty($this->selfServiceSettings->relabelFields[$key]) ? $defaultLabel : $this->selfServiceSettings->relabelFields[$key];
	}
	/**
	 * Returns the meta HTML code for each input field.
	 *
	 * Calling this method does not require the existence of an enclosing {@link accountContainer}. 
	 *  
	 * It is not possible to display help links.
	 *
	 * @param array $fields list of active fields
	 * @param array $attributes attributes of LDAP account
	 * @param boolean $passwordChangeOnly indicates that the user is only allowed to change his password and no LDAP content is readable
	 * @param array $readOnlyFields list of read-only fields
	 * @return array list of meta HTML elements (field name => htmlResponsiveRow)
	 *
	 * @see htmlElement
	 */
	public function getSelfServiceOptions($fields, $attributes, $passwordChangeOnly, $readOnlyFields) {
		// this function must be overwritten by subclasses.
		return array();
	}
	/**
	 * Checks if all input values are correct and returns the LDAP attributes which should be changed.
	 *  Return values:
	 *  messages: array of parameters to create status messages
	 *  add: array of attributes to add
	 *  del: array of attributes to remove
	 *  mod: array of attributes to modify
	 *  info: array of values with informational value (e.g. to be used later by pre/postModify actions)
	 *
	 * Calling this method does not require the existence of an enclosing {@link accountContainer}.
	 *
	 * @param string $fields input fields
	 * @param array $attributes LDAP attributes
	 * @param boolean $passwordChangeOnly indicates that the user is only allowed to change his password and no LDAP content is readable
	 * @param array $readOnlyFields list of read-only fields
	 * @return array messages and attributes (array('messages' => array(), 'add' => array('mail' => array('test@test.com')), 'del' => array(), 'mod' => array(), 'info' => array()))
	 */
	public function checkSelfServiceOptions($fields, $attributes, $passwordChangeOnly, $readOnlyFields) {
		$return = array('messages' => array(), 'add' => array(), 'del' => array(), 'mod' => array(), 'info' => array());
		return $return;
	}
	/**
	 * Returns a list of self service configuration settings.
	 *
	 * Calling this method does not require the existence of an enclosing {@link accountContainer}. 
	 *  
	 * The name attributes are used as keywords to load
	 * and save settings. We recommend to use the module name as prefix for them
	 * (e.g. posixAccount_homeDirectory) to avoid naming conflicts.
	 *
	 * @param selfServiceProfile $profile currently edited profile
	 * @return htmlElement meta HTML object
	 *
	 * @see baseModule::get_metaData()
	 * @see htmlElement
	 */
	public function getSelfServiceSettings($profile) {
		if (isset($this->meta['selfServiceSettings'])) {
			return $this->meta['selfServiceSettings'];
		}
		else {
			return array();
		}
	}
	/**
	 * Checks if the self service settings are valid.
	 *
	 * Calling this method does not require the existence of an enclosing {@link accountContainer}. 
	 *  
	 * If the input data is invalid the return value is an array that contains arrays
	 * to build StatusMessages (message type, message head, message text). If no errors
	 * occured the function returns an empty array.
	 *
	 * @param array $options hash array (option name => value) that contains the input. The option values are all arrays containing one or more elements.
	 * @param selfServiceProfile $profile self service profile
	 * @return array error messages
	 */
	public function checkSelfServiceSettings(&$options, &$profile) {
		// needs to be implemented by the subclasses, if needed
		return array();
	}
	/**
	 * Allows the module to run commands before the LDAP entry is changed or created.
	 *
	 * An error message should be printed if the function returns false.
	 *
	 * @param boolean $newAccount is new account or existing one
	 * @param array $attributes LDAP attributes of this entry
	 * @return boolean true, if no problems occured
	 */
	public function preModifySelfService($newAccount, $attributes) {
		return true;
	}
	/**
	 * Allows the module to run commands after the LDAP entry is changed or created.
	 *
	 * @param boolean $newAccount is new account or existing one
	 * @param array $attributes LDAP attributes of this entry
	 * @return boolean true, if no problems occured
	 */
	public function postModifySelfService($newAccount, $attributes) {
		return true;
	}
	/**
	 * This allows modules to create a link to a module specific page
	 * for the self service.
	 * The link is shown on the login page of the self service. You
	 * can use this to provide e.g. a page to reset passwords.
	 *
	 * @param array $settings self service settings
	 * @return String link text (null if no special page used)
	 */
	public function getLinkToSpecialSelfServicePage($settings) {
		return null;
	}
	/**
	 * This function creates meta HTML code to display the module specific page
	 * for the self service.
	 *
	 * @param selfServiceProfile $profile self service settings
	 * @return htmlElement meta HTML object
	 *
	 * @see htmlElement
	 */
	public function displaySpecialSelfServicePage($profile) {
		return null;
	}
	/**
	 * Returns the {@link accountContainer} object.
	 *
	 * @return accountContainer accountContainer object
	 *
	 * @see accountContainer
	 */
	protected function getAccountContainer() {
		if (isset($this->base) && isset($_SESSION[$this->base])) {
			return $_SESSION[$this->base];
		}
		else {
			return null;
		}
	}
	/**
	 * Returns the LDAP attributes which are managed in this module.
	 *
	 * @return array attributes
	 */
	public function getAttributes() {
		return $this->attributes;
	}
	/**
	 * Returns the LDAP attributes which are managed in this module (with unchanged values).
	 *
	 * @return array attributes
	 */
	public function getOriginalAttributes() {
		return $this->orig;
	}
	/**
	 * Returns the path to the module icon.
	 * The path must be releative to graphics (e.g. key.png) or an URL (/icons/icon.png or http://server/icon.png).
	 * You can also set $this->meta['icon']. The preferred size is 32x32px.
	 *
	 * @return unknown
	 *
	 * @see baseModule::get_metaData()
	 */
	public function getIcon() {
		if (isset($this->meta['icon'])) {
			return $this->meta['icon'];
		}
		return null;
	}
	/**
	 * Manages AJAX requests.
	 * This function may be called with or without an account container.
	 */
	public function handleAjaxRequest() {
		// modules that use AJAX need to implement this function
	}
	/**
	 * Specifies if this module supports the LAM admin interface.
	 * The LAM admin interface are the pages that allow to manage e.g. users and groups.
	 * In contrast there is also the LAM self service interface. Most modules support
	 * the admin interface.
	 *
	 * @return boolean support admin interface
	 */
	public function supportsAdminInterface() {
		return true;
	}
	/**
	 * Returns a list of jobs that can be run.
	 *
	 * @param LAMConfig $config configuration
	 */
	public function getSupportedJobs(&$config) {
		return array();
	}
	// helper functions
	/**
	 * Returns if the given configuration option is set.
	 * This function returns false if the configuration options cannot be read.
	 *
	 * @param String $optionName name of the option
	 * @param boolean $default default value if config option is not set at all (default: false)
	 * @return boolean true if option is set
	 */
	protected function isBooleanConfigOptionSet($optionName, $default = false) {
		// abort if configuration is not available
		if (!isset($this->moduleSettings) || !is_array($this->moduleSettings) || !isset($this->moduleSettings[$optionName][0])) {
			return $default;
		}
		return ($this->moduleSettings[$optionName][0] == 'true');
	}
	/**
	 * Returns a list of wildcards that can be replaced in input fileds.
	 * E.g. "$firstname$" is replaced with "givenName" attribute value.
	 *
	 * @return array replacements as wildcard => value
	 */
	public function getWildCardReplacements() {
		return array();
	}
}
?> |