*
* Location and naming of modules
* All LAM modules are placed in lib/modules/ and are named ".inc". * E.g. if you create a new module and its class name is "qmail" then the filename would be "qmail.inc". * The class name of a module must contain only a-z, A-Z, 0-9, -, and _.
*
* You can avoid to override many functions by using {@link get_metaData()}.
*
* All module classes should extend the baseModule class. * * @package modules * @author Roland Gruber */ abstract class baseModule { /** includes all meta data provided by the sub class */ protected $meta; /** the account type of this module (user, group, host) */ private $scope; /** configuration settings of all modules */ protected $moduleSettings; /** * self service profile with settings of all modules * @var selfServiceProfile profile */ protected $selfServiceSettings; /** name of parent accountContainer ($_SESSION[$base]) */ private $base; /** contains all ldap attributes which should be written */ protected $attributes; /** contains all ldap attributes which are loaded from ldap */ protected $orig; /** contains all error messages of a module */ protected $messages; /** if true, managed object classes are added when an account is created or loaded (default: true) */ protected $autoAddObjectClasses = true; /** * Creates a new base module class * * @param string $scope the account type (user, group, host) */ public function __construct($scope) { $this->scope = $scope; // load configuration if ($this->can_manage() || ($scope == 'none')) { if (isset($_SESSION['config'])) { $this->moduleSettings = $_SESSION['config']->get_moduleSettings(); } if (isset($_SESSION['selfServiceProfile'])) { $this->selfServiceSettings = $_SESSION['selfServiceProfile']; } // initialize module $this->load_Messages(); $this->meta = $this->get_metaData(); } } /** * This function fills the $messages variable with output messages from this module. * * Calling this method requires the existence of an enclosing {@link accountContainer}. */ protected function load_Messages() { } /** * Initializes the module after it became part of an {@link accountContainer} * * Calling this method requires the existence of an enclosing {@link accountContainer}. * * @param string $base the name of the {@link accountContainer} object ($_SESSION[$base]) */ public function init($base) { $this->base = $base; $this->attributes = array(); $this->orig = array(); // add object classes if needed $this->attributes['objectClass'] = array(); $this->orig['objectClass'] = array(); if ($this->autoAddObjectClasses === true) { $objectClasses = $this->getManagedObjectClasses($this->getAccountContainer()->get_type()->getId()); for ($i = 0; $i < sizeof($objectClasses); $i++) { if (!in_array($objectClasses[$i], $this->attributes['objectClass'])) { $this->attributes['objectClass'][] = $objectClasses[$i]; } } } } /** * This function loads the LDAP attributes when an account should be loaded. * * Calling this method requires the existence of an enclosing {@link accountContainer}.
*
* By default this method loads the object classes and accounts which are specified in {@link getManagedObjectClasses()} * and {@link getManagedAttributes()}. * * @param array $attributes array like the array returned by get_ldap_attributes(dn of account) but without count indices */ public function load_attributes($attributes) { $this->attributes = array(); $this->attributes = array(); // load object classes if (isset($attributes['objectClass'])) { $this->attributes['objectClass'] = $attributes['objectClass']; $this->orig['objectClass'] = $attributes['objectClass']; } else { $this->attributes['objectClass'] = array(); $this->orig['objectClass'] = array(); } $typeId = $this->getAccountContainer()->get_type()->getId(); // add object classes if needed if ($this->autoAddObjectClasses === true) { $objectClasses = $this->getManagedObjectClasses($typeId); for ($i = 0; $i < sizeof($objectClasses); $i++) { if (!in_array($objectClasses[$i], $this->attributes['objectClass'])) { $this->attributes['objectClass'][] = $objectClasses[$i]; } } } // load attributes $attributeNames = array_merge($this->getManagedAttributes($typeId), $this->getManagedHiddenAttributes($typeId)); $attributeNames = array_unique($attributeNames); $attributeNames = array_values($attributeNames); for ($i = 0; $i < sizeof($attributeNames); $i++) { if (isset($attributes[$attributeNames[$i]])) { $this->attributes[$attributeNames[$i]] = $attributes[$attributeNames[$i]]; $this->orig[$attributeNames[$i]] = $attributes[$attributeNames[$i]]; } } } /** * This function provides meta data which is interpreted by baseModule. * Only subclasses will return real data.
*
* The aim of the meta data is to reduce the number * of functions in the subclasses. All major data is centralized in one place.
*
* The returned array contains a list of key-value pairs for the different functions.
* * Example: return array("is_base" => true); * * @return array meta data */ public function get_metaData() { return array(); } /** * Returns the account type of this module (user, group, host) * * @return string account type */ public function get_scope() { return $this->scope; } /** * Returns true if this module can manage accounts of the current type, otherwise false. * * Calling this method does not require the existence of an enclosing {@link accountContainer}. * * @return boolean true if module fits * * @see baseModule::get_metaData() */ public abstract function can_manage(); /** * Returns true if your module is a base module and otherwise false. * * Calling this method does not require the existence of an enclosing {@link accountContainer}.
*
* Every account type needs exactly one base module. A base module manages a structural object class. * E.g. the inetOrgPerson module is a base module since its object class is structural. * * @return boolean true if base module (defaults to false if no meta data is provided) * * @see baseModule::get_metaData() */ public function is_base_module() { return (isset($this->meta['is_base']) && ($this->meta['is_base'])); } /** * Returns an LDAP filter for the account lists * * Calling this method does not require the existence of an enclosing {@link accountContainer}.
*
* Returns an array('or' => '...', 'and' => '...') that is used to build the LDAP filter. Usually, this is used to filter object classes. * All "or" filter parts of the base modules are combined with OR and then combined with the "and" parts.
* The resulting LDAP filter will look like this: (&(|(OR1)(OR2)(OR3))(AND1)(AND2)(AND3))
*
* Example: return array('or' => '(objectClass=posixAccount)', 'and' => '(!(uid=*$))') * * @param string $typeId account type id * @return string LDAP filter * * @see baseModule::get_metaData() */ public function get_ldap_filter($typeId) { if (isset($this->meta['ldap_filter'])) { return $this->meta['ldap_filter']; } return ""; } /** * Returns an alias name for the module. * * Calling this method does not require the existence of an enclosing {@link accountContainer}.
*
* This function returns a more descriptive string than the class name. Alias names are used for the buttons on the account pages and the module selection in the configuration wizard.
* Please take care that your alias name is not too long. It may contain any character but should not include parts that may be interpreted by the browser (e.g. '<' or '>'). * If you use different aliases dependent on the account type please make sure that there is a general alias for unknown types. * * @return string alias name * * @see baseModule::get_metaData() */ public function get_alias() { if (isset($this->meta['alias'])) { return $this->meta['alias']; } return get_class($this); } /** * Returns a hash array containing a list of possible LDAP attributes that can be used to form the RDN (Relative Distinguished Name). * * Calling this method does not require the existence of an enclosing {@link accountContainer}.
*
* The returned elements have this form: => *
is the name of the LDAP attribute *
defines the priority of the attribute (can be "low", "normal", "high")
*
* Example: return array('uid' => 'normal', 'cn' => 'low') * * @param string $typeId account type * @return array list of attributes * * @see baseModule::get_metaData() */ public function get_RDNAttributes($typeId) { if (isset($this->meta['RDN'])) { return $this->meta['RDN']; } return array(); } /** * This function returns a list with all depending and conflicting modules. * * Calling this method does not require the existence of an enclosing {@link accountContainer}.
*
* The return value is an array with two sub arrays, "depends" and "conflicts". * All values of the conflict array are string values with module names. All values of the depends * array are either string values with module names or arrays which include only string values with * module names.
* If an element of the depends array is itself an array, this means that your module * depends on one of these modules.
*
* Example: return array("depends" => array("posixAccount", array("qmail", "sendmail")), "conflicts" => array("exim")) * * @return array list of dependencies and conflicts * * @see baseModule::get_metaData() */ public function get_dependencies() { if (isset($this->meta['dependencies'])) { return $this->meta['dependencies']; } return array('depends' => array(), 'conflicts' => array()); } /** * This function defines what attributes will be used in the account profiles and their appearance in the profile editor. * * Calling this method does not require the existence of an enclosing {@link accountContainer}.
*
* The return value is an object implementing htmlElement.
* The field name are used as keywords to load * and save profiles. We recommend to use the module name as prefix for them * (e.g. posixAccount_homeDirectory) to avoid naming conflicts. * * @param string $typeId type id (user, group, host, ...) * @return htmlElement meta HTML object * * @see baseModule::get_metaData() * @see htmlElement */ public function get_profileOptions($typeId) { if (isset($this->meta['profile_options'])) { return $this->meta['profile_options']; } return array(); } /** * Checks input values of account profiles. * * Calling this method does not require the existence of an enclosing {@link accountContainer}.
*
* $options is an hash array (option name => value) that contains the user input. * The option values are all arrays containing one or more elements.
* 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 a hash array (name => value) containing the user input * @param string $typeId type id (user, group, host) * @return array list of error messages (array(type, title, text)) to generate StatusMessages, if any * * @see baseModule::get_metaData() */ public function check_profileOptions($options, $typeId) { $errors = array(); if (isset($this->meta['profile_checks'])) { foreach ($this->meta['profile_checks'] as $identifier => $check) { // check if option is required if (isset($check['required']) && $check['required'] && (!isset($options[$identifier][0]) || ($options[$identifier][0] == ''))) { $errors[] = $check['required_message']; continue; } switch ($check['type']) { // check by regular expression (from account.inc) case "ext_preg": // ignore empty fields if (!empty($options[$identifier][0]) && !get_preg($options[$identifier][0], $check['regex'])) { $errors[] = $check['error_message']; } break; // check by regular expression (case insensitive) case 'regex_i': // ignore empty fields if (!empty($options[$identifier][0]) && !preg_match('/' . $check['regex'] . '/i', $options[$identifier][0])) { $errors[] = $check['error_message']; } break; // check by regular expression (case sensitive) case 'regex': // ignore empty fields if (!empty($options[$identifier][0]) && !preg_match('/' . $check['regex'] . '/', $options[$identifier][0])) { $errors[] = $check['error_message']; } break; // check by integer comparison (greater) case 'int_greater': $val1 = $options[$check['cmp_name1']][0]; $val2 = $options[$check['cmp_name2']][0]; // ignore if both fields are empty if (!(empty($val1) && empty($val2)) && (($val1 == '') || ($val2 == '') || !(intval($val1) > intval($val2)))) { $errors[] = $check['error_message']; } break; // check by integer comparison (greater or equal) case 'int_greaterOrEqual': $val1 = $options[$check['cmp_name1']][0]; $val2 = $options[$check['cmp_name2']][0]; // ignore if both fields are empty if (!(empty($val1) && empty($val2)) && (($val1 == '') || ($val2 == '') || !(intval($val1) >= intval($val2)))) { $errors[] = $check['error_message']; } break; // print error message for invalid types default: StatusMessage("ERROR", "Unsupported type!", $check['type']); break; } } } return $errors; } /** * This function loads the values from an account profile to the module's internal data structures. * * Calling this method does not require the existence of an enclosing {@link accountContainer}. * * @param array $profile hash array with profile values (identifier => value) * * @see baseModule::get_metaData() */ public function load_profile($profile) { if (isset($this->meta['profile_mappings'])) { $identifiers = array_keys($this->meta['profile_mappings']); for ($i = 0; $i < sizeof($identifiers); $i++) { if (isset($profile[$identifiers[$i]])) { $this->attributes[$this->meta['profile_mappings'][$identifiers[$i]]] = $profile[$identifiers[$i]]; } } } } /** * Returns a list of configuration options. * * Calling this method does not require the existence of an enclosing {@link accountContainer}.
*
* The field names 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 array $scopes account types (user, group, host) * @param array $allScopes list of all active account modules and their account type id (module => array(type id)) * @return mixed htmlElement or array of htmlElement * * @see baseModule::get_metaData() * @see htmlElement */ public function get_configOptions($scopes, $allScopes) { $return = array(); for ($i = 0; $i < sizeof($scopes); $i++) { if (isset($this->meta['config_options'][$scopes[$i]])) { if (is_array($this->meta['config_options'][$scopes[$i]])) { $return = array_merge($return, $this->meta['config_options'][$scopes[$i]]); } elseif (isset($return[0]) && ($return[0] instanceof htmlTable) && ($this->meta['config_options'][$scopes[$i]] instanceof htmlTable)) { $return[0]->mergeTableElements($this->meta['config_options'][$scopes[$i]]); } else { $return[] = $this->meta['config_options'][$scopes[$i]]; } } } if (isset($this->meta['config_options']['all'])) { if (is_array($this->meta['config_options']['all'])) { $return = array_merge($return, $this->meta['config_options']['all']); } elseif (isset($return[0]) && ($return[0] instanceof htmlTable) && ($this->meta['config_options']['all'] instanceof htmlTable)) { $return[0]->mergeTableElements($this->meta['config_options']['all']); } else { $return[] = $this->meta['config_options']['all']; } } return $return; } /** * Checks input values of module settings. * * 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 subarrays to build StatusMessages ('message type', 'message head', 'message text'). *
If no errors occured the function returns an empty array. * * @param array $typeIds list of account type ids which are used * @param array $options hash array (option name => value) that contains the input. The option values are all arrays containing one or more elements. * @return array list of error messages * * @see baseModule::get_metaData() */ public function check_configOptions($typeIds, &$options) { $messages = array(); // add checks that are independent of scope $scopes = array('all'); foreach ($typeIds as $typeId) { $scopes[] = getScopeFromTypeId($typeId); } $scopes = array_unique($scopes); for ($s = 0; $s < sizeof($scopes); $s++) { if (isset($this->meta['config_checks'][$scopes[$s]]) && is_array($this->meta['config_checks'][$scopes[$s]])) { $identifiers = array_keys($this->meta['config_checks'][$scopes[$s]]); for ($i = 0; $i < sizeof($identifiers); $i++) { // check if option is required if (isset($this->meta['config_checks'][$scopes[$s]][$identifiers[$i]]['required']) && ($this->meta['config_checks'][$scopes[$s]][$identifiers[$i]]['required']) && ($options[$identifiers[$i]][0] == '')) { $messages[] = $this->meta['config_checks'][$scopes[$s]][$identifiers[$i]]['required_message']; } switch ($this->meta['config_checks'][$scopes[$s]][$identifiers[$i]]['type']) { // check by regular expression (from account.inc) case "ext_preg": // ignore empty fields if ($options[$identifiers[$i]][0] == '') { break; } if (! get_preg($options[$identifiers[$i]][0], $this->meta['config_checks'][$scopes[$s]][$identifiers[$i]]['regex'])) { $messages[] = $this->meta['config_checks'][$scopes[$s]][$identifiers[$i]]['error_message']; } break; // check by regular expression (case insensitive) case "regex_i": // ignore empty fields if ($options[$identifiers[$i]][0] == '') { break; } if (! preg_match('/' . $this->meta['config_checks'][$scopes[$s]][$identifiers[$i]]['regex'] . '/i', $options[$identifiers[$i]][0])) { $messages[] = $this->meta['config_checks'][$scopes[$s]][$identifiers[$i]]['error_message']; } break; // check by regular expression (case sensitive) case "regex": // ignore empty fields if ($options[$identifiers[$i]][0] == '') { break; } if (! preg_match('/' . $this->meta['config_checks'][$scopes[$s]][$identifiers[$i]]['regex'] . '/', $options[$identifiers[$i]][0])) { $messages[] = $this->meta['config_checks'][$scopes[$s]][$identifiers[$i]]['error_message']; } break; // check by integer comparison (greater) case "int_greater": // ignore if both fields are empty if (($options[$this->meta['config_checks'][$scopes[$s]][$identifiers[$i]]['cmp_name1']][0] == '') && ($options[$this->meta['config_checks'][$scopes[$s]][$identifiers[$i]]['cmp_name2']][0] == '')) { break; } // print error message if only one field is empty if (($options[$this->meta['config_checks'][$scopes[$s]][$identifiers[$i]]['cmp_name1']][0] == '') || ($options[$this->meta['config_checks'][$scopes[$s]][$identifiers[$i]]['cmp_name2']][0] == '')) { $messages[] = $this->meta['config_checks'][$scopes[$s]][$identifiers[$i]]['error_message']; break; } // compare if (!(intval($options[$this->meta['config_checks'][$scopes[$s]][$identifiers[$i]]['cmp_name1']][0]) > intval($options[$this->meta['config_checks'][$scopes[$s]][$identifiers[$i]]['cmp_name2']][0]))) { $messages[] = $this->meta['config_checks'][$scopes[$s]][$identifiers[$i]]['error_message']; } break; // check by integer comparison (greater or equal) case "int_greaterOrEqual": // ignore if both fields are empty if (($options[$this->meta['config_checks'][$scopes[$s]][$identifiers[$i]]['cmp_name1']][0] == '') && ($options[$this->meta['config_checks'][$scopes[$s]][$identifiers[$i]]['cmp_name2']][0] == '')) { break; } // print error message if only one field is empty if (($options[$this->meta['config_checks'][$scopes[$s]][$identifiers[$i]]['cmp_name1']][0] == '') || ($options[$this->meta['config_checks'][$scopes[$s]][$identifiers[$i]]['cmp_name2']][0] == '')) { $messages[] = $this->meta['config_checks'][$scopes[$s]][$identifiers[$i]]['error_message']; break; } // compare if (!(intval($options[$this->meta['config_checks'][$scopes[$s]][$identifiers[$i]]['cmp_name1']][0]) >= intval($options[$this->meta['config_checks'][$scopes[$s]][$identifiers[$i]]['cmp_name2']][0]))) { $messages[] = $this->meta['config_checks'][$scopes[$s]][$identifiers[$i]]['error_message']; } break; // print error message on undefined type default: StatusMessage("ERROR", "Unsupported type!", $this->meta['config_checks'][$scopes[$s]][$identifiers[$i]]['type']); break; } } } } return $messages; } /** * Returns a hashtable with all entries that may be printed out in the PDF. * * @param string $typeId type id (user, group, host) * @return array PDF entries as key => label * * @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; } /** * Adds an image to the PDF. * * @param array $result result array (entry will be added here) * @param String $attrName attribute name * @param PDFTable $table table */ public function addPDFImage(&$result, $attrName) { if (isset($this->attributes[$attrName]) && (sizeof($this->attributes[$attrName]) > 0)) { $result[get_class($this) . '_' . $attrName][] = new PDFImage($this->attributes[$attrName][0]); } } /** * 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 function 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 * @param ConfiguredType $type account type * @return array column list * * @see baseModule::get_metaData() */ public function get_uploadColumns($selectedModules, &$type) { if (isset($this->meta['upload_columns'])) { return $this->meta['upload_columns']; } 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']; } 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 * @param ConfiguredType $type account type * @return array list of error messages if any */ public function build_uploadAccounts($rawAccounts, $ids, &$partialAccounts, $selectedModules, &$type) { // 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|String[] $regex 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]])) { $regexIDs = is_array($regex) ? $regex : array($regex); // single value if ($regexSplit == null) { if (!empty($regex)) { $this->checkUploadRegex($regexIDs, $rawAccounts[$position][$ids[$colName]], $message, $position, $errors); } $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 (!$this->checkUploadRegex($regexIDs, $list[$x], $message, $position, $errors)) { break; } } } } } } /** * Checks the upload value against a list of regular expressions. * * @param string[] $regexIDs regular expression IDs for get_preg() * @param string $value value to check * @param array $message error message array if not matching * @param int $position upload position * @param array $errors error messages * @return value is ok * @see get_preg() */ private function checkUploadRegex($regexIDs, $value, $message, $position, &$errors) { $matched = false; foreach ($regexIDs as $regexID) { if (get_preg($value, $regexID)) { $matched = true; break; } } if (!$matched) { $errMsg = $message; array_push($errMsg, array($position)); $errors[] = $errMsg; return false; } return true; } /** * 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 alphanumeric characters. No HTML/CSS elements are allowed.
  • *
  • Text (required)
    * The text of the help entry which may contain any alphanumeric 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) * @param ConfiguredType $type account type * @return array array which contains status messages. Each entry is an array containing the status message parameters. */ public function doUploadPreActions($attributes, $type) { 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 * @param string[] $selectedModules selected account modules * @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 *
) * @param ConfiguredType $type account type */ public function doUploadPostActions(&$data, $ids, $failed, &$temp, &$accounts, $selectedModules, $type) { 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 null; } /** * 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/htmlResponsiveRow. * 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|htmlResponsiveRow $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; } if ($container instanceof htmlResponsiveRow) { $input = new htmlResponsiveInputTextarea($attrName, $value, $cols, 3, $label, $attrName); } else { $input = new htmlTableExtendedInputTextarea($attrName, $value, $cols, 3, $label, $attrName); } } else { if ($container instanceof htmlResponsiveRow) { $input = new htmlResponsiveInputField($label, $attrName, $value, $attrName); } else { $input = new htmlTableExtendedInputField($label, $attrName, $value, $attrName); } if ($length != null) { $input->setFieldSize($length); } if (!empty($autoCompleteValues)) { $input->enableAutocompletion($autoCompleteValues); } } $input->setRequired($required); if ($container instanceof htmlResponsiveRow) { $container->add($input, 12); } else { $container->addElement($input, true); } return $input; } /** * Adds a simple read-only field to the given container. * * @param htmlTable|htmlResponsiveRow $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; } if ($container instanceof htmlTable) { $container->addElement($labelBox); $container->addElement(new htmlOutputText($val, false), true); } else { $container->addLabel($labelBox); $container->addField(new htmlOutputText($val, false)); } } /** * 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|htmlResponsiveRow $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 * @param string $cssClasses additional CSS classes of input fields */ protected function addMultiValueInputTextField(&$container, $attrName, $label, $required = false, $length = null, $isTextArea = false, $autoCompleteValues = null, $fieldSize = null, &$htmlIDs = null, $cssClasses = '') { $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, true, $required); $labelTextOut->alignment = htmlElement::ALIGN_TOP; if ($container instanceof htmlTable) { $container->addElement($labelTextOut); } else { $container->addLabel($labelTextOut); } } $help = new htmlHelpLink($attrName); $help->alignment = htmlElement::ALIGN_TOP; $subContainer = new htmlTable(); $subContainer->setCSSClasses(array('fullwidth')); $subContainer->alignment = htmlElement::ALIGN_TOP; for ($i = 0; $i < sizeof($values); $i++) { if (!$isTextArea) { $input = new htmlInputField($attrName . '_' . $i, $values[$i]); if (!empty($cssClasses)) { $input->setCSSClasses(array($cssClasses)); } 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; } $textArea = new htmlInputTextarea($attrName . '_' . $i, $values[$i], $cols, 3); if (!empty($cssClasses)) { $textArea->setCSSClasses(array($cssClasses)); } $subContainer->addElement($textArea); } if (!empty($htmlIDs)) { $htmlIDs[] = $attrName . '_' . $i; } if (!empty($values[$i])) { $delButton = new htmlButton('del_' . $attrName . '_' . $i, 'del.png', true); $delButton->setCSSClasses(array('noMarginSides')); $subContainer->addElement($delButton); } if ($i == 0) { $addButton = new htmlButton('add_' . $attrName, 'add.png', true); $addButton->setCSSClasses(array('noMarginSides')); $subContainer->addElement($addButton); if ($container instanceof htmlResponsiveRow) { $subContainer->addElement($help); } } $subContainer->addNewLine(); } if ($container instanceof htmlTable) { $container->addElement($subContainer); $container->addElement($help, true); } else { if ($label !== null) { $container->addField($subContainer); } else { $container->add($subContainer, 12); } } } /** * 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 htmlResponsiveRow $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->setMarkAsRequired($required); $container->addLabel($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); $input->setHasDescriptiveElements($hasDescriptiveOptions); $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->addElement(new htmlHelpLink($attrName)); } $subContainer->addNewLine(); } $container->addField($subContainer); } /** * 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 an area with two multi-select fields with buttons to move items from right to left and vice-versa. * The options of the selects must be presorted. *
Names: *
    *
  • First select: $namePrefix_1 *
  • Second select: $namePrefix_2 *
  • Button move left: $namePrefix_left *
  • Button move right: $namePrefix_right *
* * @param htmlResponsiveRow $container row * @param string $labelFirst label of first selct * @param string $labelSecond label of second select * @param string[] $optionsFirst options of first select ('label' => 'value') * @param string[] $selectedFirst selected options of first select * @param string[] $optionsSecond options of first select ('label' => 'value') * @param string[] $selectedSecond selected options of second select * @param string $namePrefix prefix for select field and button names * @param bool $rightToLeftText sets the text direction in select to right to left * @param bool $showFilter displays a live filter */ protected function addDoubleSelectionArea(&$container, $labelFirst, $labelSecond, $optionsFirst, $selectedFirst, $optionsSecond, $selectedSecond, $namePrefix, $rightToLeftText = false, $showFilter = false) { // first select $firstRow = new htmlResponsiveRow(); $firstRow->add(new htmlOutputText($labelFirst), 12); $firstSelect = new htmlSelect($namePrefix . '_1', $optionsFirst, $selectedFirst, 15); $firstSelect->setHasDescriptiveElements(true); $firstSelect->setMultiSelect(true); $firstSelect->setRightToLeftTextDirection($rightToLeftText); $firstSelect->setSortElements(false); $firstRow->add($firstSelect, 12); if ($showFilter) { $firstFilterGroup = new htmlGroup(); $firstFilterGroup->addElement(new htmlOutputText(_('Filter'))); $firstFilterInput = new htmlInputField($namePrefix . '_filterFirst'); $firstFilterInput->filterSelectBox($namePrefix . '_1'); $firstFilterGroup->addElement($firstFilterInput); $firstRow->add($firstFilterGroup, 12); } $container->add($firstRow, 12, 5); // buttons $buttonRow = new htmlResponsiveRow(); $buttonRow->setCSSClasses(array('text-center')); $buttonRow->add(new htmlSpacer(null, '1rem'), 0, 12); $buttonRow->add(new htmlButton($namePrefix . '_left', 'back.gif', true), 0, 12); $buttonRow->add(new htmlButton($namePrefix . '_left', 'up.gif', true), 6, 0); $buttonRow->add(new htmlButton($namePrefix . '_right', 'forward.gif', true), 0, 12); $buttonRow->add(new htmlButton($namePrefix . '_right', 'down.gif', true), 6, 0); $container->add($buttonRow, 12, 2); // second select $secondRow = new htmlResponsiveRow(); $secondRow->add(new htmlOutputText($labelSecond), 12); $secondSelect = new htmlSelect($namePrefix . '_2', $optionsSecond, $selectedSecond, 15); $secondSelect->setHasDescriptiveElements(true); $secondSelect->setMultiSelect(true); $secondSelect->setRightToLeftTextDirection($rightToLeftText); $secondSelect->setSortElements(false); $secondRow->add($secondSelect, 12); if ($showFilter) { $secondFilterGroup = new htmlGroup(); $secondFilterGroup->addElement(new htmlOutputText(_('Filter'))); $secondFilterInput = new htmlInputField($namePrefix . '_filterSecond'); $secondFilterInput->filterSelectBox($namePrefix . '_2'); $secondFilterGroup->addElement($secondFilterInput); $secondRow->add($secondFilterGroup, 12); } $container->add($secondRow, 12, 5); } /** * 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]; } } } /** * 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 addMultiValueSelfServiceTextField(&$container, $name, $label, &$fields, &$attributes, &$readOnlyFields, $required = false, $isTextArea = false) { $values = array(); if (isset($attributes[$name][0])) { $values = $attributes[$name]; } $readOnly = in_array($name, $readOnlyFields); $field = new htmlResponsiveRow(); if (!$readOnly) { if (empty($values)) { $values[] = ''; } $fieldNamePrefix = get_class($this) . '_' . $name . '_'; for ($i = 0; $i < sizeof($values); $i++) { $fieldRow = new htmlResponsiveRow(); $value = $values[$i]; if (!$isTextArea) { $inputField = new htmlInputField($fieldNamePrefix . $i, $value); $inputField->setRequired($required); $inputField->setFieldSize(null); } else { $inputField = new htmlInputTextarea($fieldNamePrefix . $i, $value, null, null); } $fieldRow->add($inputField, 9); if (!empty($value)) { $linkGroup = new htmlGroup(); $delLink = new htmlLink(null, '#', '../../graphics/del.png'); $delLink->setOnClick('window.lam.selfservice.delMultiValue(\'' . $fieldNamePrefix . '\', this); return false;'); $delLink->setCSSClasses(array('del-link')); $linkGroup->addElement($delLink); if ($i === (sizeof($values) - 1)) { $addLink = new htmlLink(null, '#', '../../graphics/add.png'); $addLink->setOnClick('window.lam.selfservice.addMultiValue(\'' . $fieldNamePrefix . '\', this); return false;'); $addLink->setCSSClasses(array('add-link', 'margin-left5')); $linkGroup->addElement($addLink); } $fieldRow->add($linkGroup, 3); } $field->add($fieldRow, 12); } } else { foreach ($values as $value) { if (!$isTextArea) { $inputField = new htmlOutputText($value); } else { $inputField = new htmlOutputText($value); $inputField->setPreformatted(true); } $field->add($inputField, 12); } } $row = new htmlResponsiveRow(); $label = new htmlOutputText($this->getSelfServiceLabel($name, $label)); $label->setMarkAsRequired($required); $row->addLabel($label); $row->addField($field); $container[$name] = $row; } /** * Checks the input value of a self service multi-value 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 checkMultiValueSelfServiceTextField(&$container, $name, &$attributes, $fields, &$readOnlyFields, $validationID = null) { if (in_array($name, $fields) && !in_array($name, $readOnlyFields)) { $fieldName = get_class($this) . '_' . $name; $valuesNew = array(); foreach ($_POST as $postKey => $postValue) { if (strpos($postKey, $fieldName) === false) { continue; } if (empty($postValue)) { continue; } if (($validationID != null) && !get_preg($postValue, $validationID)) { $container['messages'][] = $this->messages[$name][0]; return; } $valuesNew[] = $postValue; } $valuesOld = isset($attributes[$name]) ? $attributes[$name] : array(); $intersect = array_intersect($valuesOld, $valuesNew); if ((sizeof($valuesOld) != sizeof($valuesNew)) || (sizeof($intersect) != sizeof($valuesOld))) { $container['mod'][$name] = $valuesNew; } } } /** * 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') * * @param string $typeId type id (user, group, host) * @return array list of object classes * * @see baseModule::get_metaData() */ public function getManagedObjectClasses($typeId) { if (isset($this->meta['objectClasses']) && is_array($this->meta['objectClasses'])) { return $this->meta['objectClasses']; } 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. * * @param string $typeId type id (user, group, host) * @return array list of aliases like array("alias name" => "attribute name") * * @see baseModule::get_metaData() */ public function getLDAPAliases($typeId) { if (isset($this->meta['LDAPaliases']) && is_array($this->meta['LDAPaliases'])) { return $this->meta['LDAPaliases']; } 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. * * @param string $typeId type id (user, group, host) * @return array list of attributes * * @see baseModule::get_metaData() */ public function getManagedAttributes($typeId) { if (isset($this->meta['attributes']) && is_array($this->meta['attributes'])) { return $this->meta['attributes']; } return array(); } /** * Returns a list of operational LDAP attributes which are managed by this module and need to be explicitly set for LDAP search. * * @param string $typeId account type id * @return array list of hidden attributes * * @see baseModule::get_metaData() */ public function getManagedHiddenAttributes($typeId) { if (isset($this->meta['hiddenAttributes']) && is_array($this->meta['hiddenAttributes'])) { return $this->meta['hiddenAttributes']; } 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']; } 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']; } 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']; } 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 null; } } /** * 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 fields. * E.g. "$firstname" is replaced with "givenName" attribute value. * * @return array replacements as wildcard => value */ public function getWildCardReplacements() { return array(); } } ?>