diff --git a/lib/api/kolab_api_service_domain.php b/lib/api/kolab_api_service_domain.php index f9910df..9190e62 100644 --- a/lib/api/kolab_api_service_domain.php +++ b/lib/api/kolab_api_service_domain.php @@ -1,269 +1,422 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | | Author: Jeroen van Meeuwen | +--------------------------------------------------------------------------+ */ /** * Service providing domain mutations */ class kolab_api_service_domain extends kolab_api_service { /** * Returns service capabilities. * * @param string $domain Domain name * * @return array Capabilities list */ public function capabilities($domain) { $auth = Auth::get_instance(); $conf = Conf::get_instance(); $domain_base_dn = $conf->get('domain_base_dn'); if (empty($domain_base_dn)) { return array(); } $effective_rights = $auth->list_rights($domain_base_dn); $rights = array(); if (in_array('add', $effective_rights['entryLevelRights'])) { $rights['add'] = "w"; } if (in_array('delete', $effective_rights['entryLevelRights'])) { $rights['delete'] = "w"; } if (in_array('modrdn', $effective_rights['entryLevelRights'])) { $rights['edit'] = "w"; } if (in_array('read', $effective_rights['entryLevelRights'])) { $rights['find'] = "r"; $rights['info'] = "r"; } $rights['effective_rights'] = "r"; return $rights; } public function domain_add($getdata, $postdata) { Log::trace("domain.add(\$getdata = " . var_export($getdata, TRUE) . ", \$postdata = " . var_export($postdata, TRUE) . ")"); $conf = Conf::get_instance(); $dna = $conf->get('domain_name_attribute'); if (empty($dna)) { $dna = 'associateddomain'; } if (empty($postdata[$dna])) { Log::error("domain.add called without '" . $dna . "' specified"); return false; } $auth = Auth::get_instance($conf->get('kolab', 'primary_domain')); // parse input attributes $attributes = $this->parse_input_attributes('domain', $postdata); $attributes[$dna] = (array) $attributes[$dna]; $domain = array_shift($attributes[$dna]); + $this->_mod_domain_attrs($domain, $attributes); + $result = $auth->domain_add($domain, $attributes); if ($result) { if ($id = $this->unique_attribute_value($result)) { $attributes['id'] = $id; } + + $this->_after_domain_created($attributes, $domain); + return $attributes; } return false; } /** * Domain delete. * * @param array $get GET parameters * @param array $post POST parameters * * @return bool True on success, False on failure * @throws kolab_api_exception */ public function domain_delete($getdata, $postdata) { Log::trace("domain.delete(\$getdata = '" . var_export($getdata, TRUE) . "', \$postdata = '" . var_export($postdata, TRUE) . "')"); if (empty($postdata['id'])) { Log::error("domain.delete called without a Domain ID"); return false; } $auth = Auth::get_instance(); // check if domain is empty if (empty($postdata['force']) || strtolower($postdata['force']) == 'false') { if (!$auth->domain_is_empty($postdata['id'])) { throw new kolab_api_exception(kolab_api_exception::DOMAIN_NOT_EMPTY); } } $result = $auth->domain_delete($postdata['id']); if ($result) { return $result; } return false; } public function domain_edit($getdata, $postdata) { Log::trace("domain.edit(\$getdata = '" . var_export($getdata, TRUE) . "', \$postdata = '" . var_export($postdata, TRUE) . "')"); if (empty($postdata['id'])) { Log::error("domain.edit called without a Domain ID"); return false; } $auth = Auth::get_instance(); // check if domain is empty when changing status to deleted, as in domain.delete if ($postdata['inetdomainstatus'] == 'deleted' && (empty($postdata['force']) || strtolower($postdata['force']) == 'false') ) { $domain = $auth->domain_info($postdata['id']); if (!empty($domain) && $domain[key($domain)]['inetdomainstatus'] != 'deleted' && !$auth->domain_is_empty($domain) ) { throw new kolab_api_exception(kolab_api_exception::DOMAIN_NOT_EMPTY); } } - $attributes = $this->parse_input_attributes('domain', $postdata); - $result = $auth->domain_edit($postdata['id'], $attributes, $postdata['type_id']); + $attributes = $this->parse_input_attributes('domain', $postdata, $postdata['type_id']); + + $this->_mod_domain_attrs(null, $attributes); + + $result = $auth->domain_edit($postdata['id'], $attributes, $postdata['type_id']); if ($result) { return $result; } return false; } public function domain_effective_rights($getdata, $postdata) { $auth = Auth::get_instance(); $conf = Conf::get_instance(); $dna = $conf->get('domain_name_attribute'); if (empty($dna)) { $dna = 'associateddomain'; } // TODO: Input validation if (!empty($getdata[$dna])) { $entry_dn = $getdata[$dna]; $unique_attr = self::unique_attribute(); $domain = $auth->domain_find_by_attribute(array($unique_attr => $entry_dn)); if (!empty($domain)) { $entry_dn = key($domain); } } else { $entry_dn = $conf->get('ldap', 'domain_base_dn'); } // TODO: Fix searching the correct base_dn... Perhaps find the entry // first. $effective_rights = $auth->list_rights($entry_dn); return $effective_rights; } public function domain_find($getdata, $postdata) { $conf = Conf::get_instance(); $dna = $conf->get('domain_name_attribute'); if (empty($dna)) { $dna = 'associateddomain'; } if (empty($getdata[$dna])) { Log::error("domain.find called without a '" . $dna . "' parameter"); return false; } $auth = Auth::get_instance(); $domain = $auth->domain_find_by_attribute(array($dna => $getdata[$dna])); if (!empty($domain)) { return $domain; } return false; } /** * Domain information. * * @param array $get GET parameters * @param array $post POST parameters * * @return array|bool Domain attributes, False on error */ public function domain_info($getdata, $postdata) { Log::trace("domain.info(\$getdata = '" . var_export($getdata, TRUE) . "', \$postdata = '" . var_export($postdata, TRUE) . "')"); if (empty($getdata['id'])) { Log::error("domain.info called without a Domain ID"); return false; } $auth = Auth::get_instance(); $attrs = $this->object_attributes('domain'); $result = $auth->domain_info($getdata['id'], $attrs); // normalize result $result = $this->parse_result_attributes('domain', $result); if (empty($result['id'])) { $result['id'] = $getdata['id']; } if ($result) { return $result; } return false; } + + /** + * Modify hosted domain attributes + */ + protected function _mod_domain_attrs($domain, &$attributes) + { + // Generate attributes (aci, inetdomainbasedn) for hosted domains + $conf = Conf::get_instance(); + if ($conf->get('kolab_wap', 'hosted_root_dn')) { + + $domain_name_attribute = $conf->get('ldap', 'domain_name_attribute'); + $hosted_root_dn = $conf->get('kolab_wap', 'hosted_root_dn'); + $mgmt_root_dn = $conf->get('kolab_wap', 'mgmt_root_dn'); + + if (empty($mgmt_root_dn)) { + $mgmt_root_dn = $conf->get('root_dn'); + } + + if (empty($domain_name_attribute)) { + $domain_name_attribute = 'associateddomain'; + } + + if (!is_array($attributes[$domain_name_attribute])) { + $attributes[$domain_name_attribute] = (array) $attributes[$domain_name_attribute]; + } + + if (empty($domain)) { + $domain = $attributes[$domain_name_attribute][0]; + } + + if (!in_array($domain, $attributes[$domain_name_attribute])) { + array_unshift($attributes[$domain_name_attribute], $domain); + } + + $domain_root_dn = 'ou=' . $domain . ',' . $hosted_root_dn; + + $aci = array( + '(targetattr = "*")' + . '(version 3.0; acl "Deny Unauthorized"; deny (all)' + . '(userdn != "ldap:///uid=kolab-service,ou=Special Users,' . $mgmt_root_dn . ' || ' + . 'ldap:///ou=People,' . $domain_root_dn . '??sub?(objectclass=inetorgperson)") AND NOT ' + . 'roledn = "ldap:///cn=kolab-admin,' . $mgmt_root_dn . '";)', + + '(targetattr != "userPassword")' + . '(version 3.0;acl "Search Access";allow (read,compare,search)' + . '(userdn = "ldap:///uid=kolab-service,ou=Special Users,' . $mgmt_root_dn . ' || ' + . 'ldap:///ou=People,' . $domain_root_dn . '??sub?(objectclass=inetorgperson)");)', + + '(targetattr = "*")' + . '(version 3.0;acl "Kolab Administrators";allow (all)' + . '(roledn = "ldap:///cn=kolab-admin,' . $domain_root_dn . ' || ' + . 'ldap:///cn=kolab-admin,' . $mgmt_root_dn . '");)' + ); + + $attributes['aci'] = $aci; + $attributes['inetdomainbasedn'] = $domain_root_dn; + + $this->is_hosted = true; + } + } + + /** + * Create LDAP object related to the new hosted domain + */ + protected function _after_domain_created($attributes, $domain) + { + if (!$this->is_hosted) { + return; + } + + $conf = Conf::get_instance(); + $ou_service = $this->controller->get_service('ou'); + $role_service = $this->controller->get_service('role'); + + $hosted_root_dn = $conf->get('kolab_wap', 'hosted_root_dn'); + $mgmt_root_dn = $conf->get('kolab_wap', 'mgmt_root_dn'); + $domain_root_dn = 'ou=' . $domain . ',' . $hosted_root_dn; + + if (empty($mgmt_root_dn)) { + $mgmt_root_dn = $conf->get('root_dn'); + } + + $ou_domain = array( + 'ou' => $domain, + 'base_dn' => $hosted_root_dn, + 'description' => $domain, + 'type_id' => 1, + ); + + $ou_domain['aci'] = array( + '(targetattr = "*")' + . '(version 3.0;acl "Deny Unauthorized"; deny (all)' + . '(userdn != "ldap:///uid=kolab-service,ou=Special Users,' . $mgmt_root_dn . ' || ' + . 'ldap:///ou=People,' . $domain_root_dn . '??sub?(objectclass=inetorgperson)") AND NOT ' + . 'roledn = "ldap:///cn=kolab-admin,' . $mgmt_root_dn . '";)', + + '(targetattr != "userPassword")' + . '(version 3.0;acl "Search Access";allow (read,compare,search,write)' + . '(userdn = "ldap:///uid=kolab-service,ou=Special Users,' . $mgmt_root_dn . ' || ' + . 'ldap:///ou=People,' . $domain_root_dn . '??sub?(objectclass=inetorgperson)");)', + + '(targetattr = "*")' + . '(version 3.0;acl "Kolab Administrators";allow (all)' + . '(roledn = "ldap:///cn=kolab-admin,' . $domain_root_dn . ' || ' + . 'ldap:///cn=kolab-admin,' . $mgmt_root_dn . '");)', + + '(target = "ldap:///ou=*,' . $domain_root_dn . '")(targetattr="objectclass || aci || ou")' + . '(version 3.0;acl "Allow Domain sub-OU Registration"; allow (add)' + . '(userdn = "ldap:///uid=kolab-service,ou=Special Users,' . $mgmt_root_dn . '");)', + + '(target = "ldap:///uid=*,ou=People,' . $domain_root_dn . '")(targetattr="*")' + . '(version 3.0;acl "Allow Domain First User Registration"; allow (add)' + . '(userdn = "ldap:///uid=kolab-service,ou=Special Users,' . $mgmt_root_dn . '");)', + + '(target = "ldap:///cn=*,' . $domain_root_dn . '")(targetattr="objectclass || cn")' + . '(version 3.0;acl "Allow Domain Role Registration"; allow (add)' + . '(userdn = "ldap:///uid=kolab-service,ou=Special Users,' . $mgmt_root_dn . '");)', + ); + + $ou_service->ou_add(null, $ou_domain); + + // Add OU trees + + foreach (array('Groups', 'People', 'Resources', 'Shared Folders') as $item) { + $ou = array( + 'ou' => $item, + 'base_dn' => $domain_root_dn, + 'type_id' => 1, + 'description' => $item, + ); + + $ou_service->ou_add(null, $ou); + } + + // Add an admin role + + $role = array( + 'cn' => 'kolab-admin', + 'description' => 'Domain Administrator', + 'type_id' => 1, + 'base_dn' => $domain_root_dn, + ); + + $role_service->role_add(null, $role); + } } diff --git a/lib/api/kolab_api_service_domain_types.php b/lib/api/kolab_api_service_domain_types.php index 487faeb..7ea999e 100644 --- a/lib/api/kolab_api_service_domain_types.php +++ b/lib/api/kolab_api_service_domain_types.php @@ -1,100 +1,136 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | | Author: Jeroen van Meeuwen | +--------------------------------------------------------------------------+ */ /** * */ class kolab_api_service_domain_types extends kolab_api_service { public static $DEFAULT_TYPE_ATTRS = array( 'auto_form_fields' => array(), 'form_fields' => array( 'associateddomain' => array( 'type' => 'list' ), 'domainrelatedobject_only' => array( 'optional' => true, ), 'inetdomainbasedn' => array( 'optional' => true, ), 'inetdomainstatus' => array( 'optional' => true, 'type' => 'select', 'values' => array( '', 'active', 'suspended', 'deleted', ), ), ), 'fields' => array( 'objectclass' => array( 'top', 'domainrelatedobject', 'inetdomain', ), ), ); + public static $HOSTED_TYPE_ATTRS = array( + 'auto_form_fields' => array(), + 'form_fields' => array( + 'associateddomain' => array( + 'type' => 'list' + ), +// 'inetdomainbasedn' => array( +// 'optional' => true, +// ), + 'inetdomainstatus' => array( + 'optional' => true, + 'type' => 'select', + 'values' => array( + '', 'active', 'suspended', 'deleted', + ), + ), + ), + 'fields' => array( + 'domainrelatedobject_only' => 1, + 'objectclass' => array( + 'top', + 'domainrelatedobject', + 'inetdomain', + ), + ), + ); + /** * Returns service capabilities. * * @param string $domain Domain name * * @return array Capabilities list */ public function capabilities($domain) { return array( 'list' => 'r', ); } /** * Domain types listing. * * @param array $get GET parameters * @param array $post POST parameters * * @return array List result with 'list' and 'count' items */ public function domain_types_list($get, $post) { // @TODO: move to database - $types = array( - 1 => array( + $types = array(); + + if ($this->conf->get('kolab_wap', 'hosted_root_dn')) { + $types[1] = array( + 'key' => 'hosted', + 'name' => 'Hosted domain', + 'description' => 'A hosted domain name space', + 'attributes' => self::$HOSTED_TYPE_ATTRS, + ); + } else { + $types[1] = array( 'key' => 'standard', 'name' => 'Standard domain', 'description' => 'A standard domain name space', 'attributes' => self::$DEFAULT_TYPE_ATTRS, - ), - ); + ); + } return array( 'list' => $types, 'count' => count($types), ); } } diff --git a/lib/kolab_api_service.php b/lib/kolab_api_service.php index b552342..c098de1 100644 --- a/lib/kolab_api_service.php +++ b/lib/kolab_api_service.php @@ -1,734 +1,742 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | | Author: Jeroen van Meeuwen | +--------------------------------------------------------------------------+ */ /** * Interface class for Kolab Admin Services */ abstract class kolab_api_service { protected $base_dn = null; protected $cache = array(); protected $conf; protected $controller; protected $db; protected $supported_types_db = array('ou', 'group', 'resource', 'role', 'sharedfolder', 'user'); protected $supported_types = array('domain', 'ou', 'group', 'resource', 'role', 'sharedfolder', 'user'); /** * Class constructor. * * @param kolab_api_controller Controller */ public function __construct($ctrl) { $this->controller = $ctrl; $this->conf = Conf::get_instance(); $this->db = SQL::get_instance(); } /** * Advertise this service's capabilities */ abstract public function capabilities($domain); /** * Returns attributes of specified user type. * * @param string $object_name Name of the object (user, group, etc.) * @param int $type_id User type identifier * @param string $key_name Reference to a variable which will be set to type key * * @return array User type attributes */ protected function object_type_attributes($object_name, $type_id, &$key_name = null) { if (!$object_name || !in_array($object_name, $this->supported_types)) { return array(); } $object_types = $this->object_types($object_name); if (empty($type_id)) { if (count($object_types) == 1) { $type_id = key($object_types); } else { throw new Exception($this->controller->translate('api.notypeid'), 34); } } else if ($type_id && empty($object_types[$type_id])) { throw new Exception($this->controller->translate('api.invalidtypeid'), 35); } $key_name = $object_types[$type_id]['key']; return $object_types[$type_id]['attributes']; } /** * Detects object type ID for specified objectClass attribute value * * @param string $object_name Name of the object (user, group, etc.) * @param array $attributes Array of attributes and values * * @return int Object type identifier */ protected function object_type_id($object_name, $attributes) { if ($object_name == 'domain') return 1; $object_class = $attributes['objectclass']; if (empty($object_class)) { return null; } $object_types = $this->object_types($object_name); if (count($object_types) == 1) { return key($object_types); } $object_class = array_map('strtolower', $object_class); $object_keys = array_diff(array_keys($attributes), array(self::unique_attribute())); $keys_count = count($object_keys); $class_count = count($object_class); $type_score = null; $type_id = null; Log::trace("kolab_api_service::object_type_id objectclasses: " . implode(", ", $object_class)); foreach ($object_types as $idx => $elem) { $ref_class = $elem['attributes']['fields']['objectclass']; if (empty($ref_class)) { continue; } Log::trace("Reference objectclasses for " . $elem['key'] . ": " . implode(", ", $ref_class)); $elem_keys_score = 0; $elem_values_score = 0; $delta = 0; // Eliminate the duplicates between the $data_ocs and $ref_ocs $_object_class = array_diff($object_class, $ref_class); $_ref_class = array_diff($ref_class, $object_class); // Object classes score $differences = count($_object_class) + count($_ref_class); $commonalities = $class_count - $differences; $elem_score = $differences > 0 ? round($commonalities / $differences, 2) : $commonalities; // Attributes score if ($keys_count) { $ref_keys = array_unique(array_merge( array_keys((array) $elem['attributes']['auto_form_fields']), array_keys((array) $elem['attributes']['form_fields']), array_keys($elem['attributes']['fields']) )); $elem_keys_score = $keys_count - count(array_diff($object_keys, $ref_keys)); } // Static attributes score $elem_values_score = 0; foreach ((array) $elem['attributes']['fields'] as $attr => $value) { // Skip the object classes we have already compared if ($attr == "objectclass") { continue; } if (!array_key_exists($attr, $attributes)) { // There's no point in comparing NULL with anything. continue; } $v = $attributes[$attr]; if (is_array($value)) { foreach ($value as $_value) { $_value = $this->conf->expand($_value, $custom = Array('base_dn' => $this->base_dn())); if (in_array($_value, (array)$v)) { $elem_values_score++; } } $value = implode('', $value); } else if (is_string($value)) { $value = $this->conf->expand($_value, $custom = Array('base_dn' => $this->base_dn())); } if (is_array($v)) { $v = implode('', $v); } if ($v == $value) { $elem_values_score += 1; } else { $elem_values_score -= 1; } } // Position in tree score if (!empty($elem['attributes']['fields']['ou'])) { if (!empty($attributes['ou'])) { if (strtolower($elem['attributes']['fields']['ou']) == strtolower($attributes['ou'])) { Log::trace("object_type " . $elem['key'] . " fields ou setting matches entry, bumping scores."); $elem_score += 2; $elem_keys_score += 10; } } } // On the likely chance that the object is a resource (types of which likely have the same // set of objectclass attribute values), consider the other attributes. (#853) if ($object_name == 'resource') { //console("From database", $elem); //console("Element key is " . $elem['key'] . " and \$attributes['mail'] is " . $attributes['mail']); if (strpos($attributes['mail'], 'resource-' . $elem['key'] . '-') === 0) { $elem_score += 10; } } // degrade class score if object contains more attributes // than defined in object type if ($keys_count && $elem_keys_score < $keys_count) { $delta -= $class_count - round(($keys_count / $elem_keys_score) * $class_count, 2); if ($delta > 0) { $elem_score -= $delta; } } $elem_score .= ':' . $elem_keys_score . ':' . $elem_values_score; // fix decimal separator for score_compare() and consistent log (#4799) $elem_score = str_replace(',', '.', $elem_score); $delta = str_replace(',', '.', $delta); Log::trace("Score for $object_name type " . $elem['name'] . ": $elem_score ($commonalities/$differences/$delta)"); // Compare last and current element (object type) score if ($this->score_compare($elem_score, $type_score)) { $type_id = $idx; $type_score = $elem_score; } } return $type_id; } /** * Returns object types definitions. * * @param string $object_name Name of the object (user, group, etc.) * * @return array Object types. */ protected function object_types($object_name) { if (!$object_name || !in_array($object_name, $this->supported_types)) { return array(); } $conf = Conf::get_instance(); $devel_mode = $conf->get('kolab_wap', 'devel_mode'); if ($devel_mode == null) { if (!empty($this->cache['object_types']) && !empty($this->cache['object_types'][$object_name])) { return $this->cache['object_types'][$object_name]; } } // get list of object types if ($object_name == 'domain') { - $object_types = array( - '1' => array( + $object_types = array(); + + if ($this->conf->get('kolab_wap', 'hosted_root_dn')) { + $object_types['1'] = array( + 'key' => 'hosted', + 'attributes' => kolab_api_service_domain_types::$HOSTED_TYPE_ATTRS, + ); + } else { + $object_types['1'] = array( 'key' => 'default', 'attributes' => kolab_api_service_domain_types::$DEFAULT_TYPE_ATTRS, - ), - ); + ); + } + $object_types['1']['attributes']['form_fields']['aci'] = array( 'type' => 'list', 'optional' => true, ); } else { $sql_result = $this->db->query("SELECT * FROM `{$object_name}_types` ORDER BY `name`"); $object_types = array(); while ($row = $this->db->fetch_assoc($sql_result)) { $object_types[$row['id']] = array(); foreach ($row as $key => $value) { if ($key != "id") { if ($key == "attributes") { $object_types[$row['id']][$key] = json_decode($value, true); } else { $object_types[$row['id']][$key] = $value; } } } } } if ($devel_mode == null) { return $this->cache['object_types'][$object_name] = $object_types; } else { return $object_types; } } /** * Parses input (for add/edit) attributes * * @param string $object_name Name of the object (user, group, etc.) * @param array $attrs Entry attributes * * @return array Entry attributes */ protected function parse_input_attributes($object_name, $attribs) { $type_attrs = $this->object_type_attributes($object_name, $attribs['type_id']); Log::trace("kolab_api_service::parse_input_attributes for $object_name: " . var_export($type_attrs, TRUE)); Log::trace("called with \$attribs: " . var_export($attribs, TRUE)); $form_service = $this->controller->get_service('form_value'); // With the result, start validating the input $attribs['object_type'] = $object_name; $validate_result = $form_service->validate(null, $attribs); $special_attr_validate = Array(); foreach ($validate_result as $attr_name => $value) { if ($value !== false && $value !== '' && $value !== null && $value !== "OK") { $special_attr_validate[$attr_name] = $value; } } Log::trace("kolab_api_service::parse_input_attributes() \$special_attr_validate: " . var_export($special_attr_validate, TRUE)); $result = array(); if (isset($type_attrs['form_fields'])) { foreach ($type_attrs['form_fields'] as $key => $value) { Log::trace("Running parse input attributes for key $key"); $type = $value['type'] ?: ($type_attrs['auto_form_fields'][$key] ? $type_attrs['auto_form_fields'][$key]['type'] : ''); if (($type == 'text' || empty($type)) && is_array($attribs[$key])) { $attribs[$key] = array_shift($attribs[$key]); } if (empty($attribs[$key]) && empty($value['optional'])) { Log::error("\$attribs['" . $key . "'] is empty, and the field is not optional"); throw new Exception("Missing input value for $key", 345); } else { Log::trace("Either \$attribs['" . $key . "'] is not empty or the field is optional"); $result[$key] = $attribs[$key]; } } } if (isset($type_attrs['auto_form_fields'])) { foreach ($type_attrs['auto_form_fields'] as $key => $value) { if (empty($attribs[$key])) { if (empty($value['optional'])) { $attribs['attributes'] = array($key); $res = $form_service->generate(null, $attribs); $attribs[$key] = $res[$key]; $result[$key] = $attribs[$key]; } } else { $result[$key] = $attribs[$key]; } } } if (isset($type_attrs['fields'])) { foreach ($type_attrs['fields'] as $key => $value) { if (!is_array($value)) { $value2 = $this->conf->expand($value, $custom = Array('base_dn' => $this->base_dn())); if ($value !== $value2) { Log::trace("Made value " . var_export($value, TRUE) . " in to: " . var_export($value2, TRUE)); $value = $value2; } } else { foreach ($value as $_key => $_value) { $_value2 = $this->conf->expand($_value, $custom = Array('base_dn' => $this->base_dn())); if ($_value !== $_value2) { Log::trace("Made value " . var_export($_value, TRUE) . " in to: " . var_export($_value2, TRUE)); $value[$_key] = $_value2; } } } if (empty($attribs[$key])) { $result[$key] = $type_attrs['fields'][$key] = $value; } else { if (!empty($type_attrs['auto_form_fields'][$key]['optional']) && $type_attrs['auto_form_fields'][$key]['type'] == "list") { $result[$key] = array_unique(array_merge((array)$attribs[$key], (array)$value)); } else { $result[$key] = $attribs[$key] = $value; } } } } // OU's parent attribute if ($object_name == 'ou' && !empty($attribs['base_dn'])) { // @TODO: validate? $result['base_dn'] = $attribs['base_dn']; } $result = array_merge($result, $special_attr_validate); Log::trace("parse_input_attributes result (merge of \$result and \$special_attr_validate)", $result); return $result; } protected function parse_list_attributes($post) { $attributes = Array(); // Attributes to return if (!empty($post['attributes']) && is_array($post['attributes'])) { // get only supported attributes $attributes = array_intersect($this->list_attribs, $post['attributes']); // need to fix array keys $attributes = array_values($attributes); // unique attribute is always allowed if (($key = array_search('id', $post['attributes'])) !== false) { $attributes[] = self::unique_attribute(); } } if (empty($attributes)) { $attributes = (array)$this->list_attribs[0]; } return $attributes; } protected function parse_list_result($result) { if (!empty($result) && !empty($result['count'])) { $unique_attr = self::unique_attribute(); // replace back unique attribute name with 'id' foreach ($result['list'] as $idx => $record) { // if not set, we assume unique attribute wasn't requested if (!isset($record[$unique_attr])) { break; } $result['list'][$idx]['id'] = $record[$unique_attr]; unset($result['list'][$idx][$unique_attr]); } } return $result; } protected function parse_list_params($post) { $params = Array(); if (!empty($post['sort_by'])) { if (is_array($post['sort_by'])) { $params['sort_by'] = Array(); foreach ($post['sort_by'] as $attrib) { if (in_array($attrib, $this->list_attribs)) { $params['sort_by'][] = $attrib; } } } else { // check if sort attribute is supported if (in_array($post['sort_by'], $this->list_attribs)) { $params['sort_by'] = $post['sort_by']; } } } if (!empty($post['sort_order'])) { $params['sort_order'] = $post['sort_order'] == 'DESC' ? 'DESC' : 'ASC'; } if (!empty($post['page'])) { $params['page'] = $post['page']; } if (!empty($post['page_size'])) { $params['page_size'] = $post['page_size']; } return $params; } protected function parse_list_search($post) { $search = Array(); // Search parameters if (!empty($post['search']) && is_array($post['search'])) { if (array_key_exists('params', $post['search'])) { $search = $post['search']; } else { $search['params'] = $post['search']; } if (!empty($post['search_operator'])) { $search['operator'] = $post['search_operator']; } } return $search; } /** * Parses result attributes * * @param string $object_name Name of the object (user, group, etc.) * @param array $attrs Entry attributes * * @return array Entry attributes */ public function parse_result_attributes($object_name, $attrs = array()) { if (empty($attrs) || !is_array($attrs)) { return $attrs; } $dn = key($attrs); $attrs = $attrs[$dn]; $extra_attrs = array(); $type_id = $this->object_type_id($object_name, $attrs); $unique_attr = self::unique_attribute(); // Search for attributes associated with the type_id that are not part // of the result returned earlier. Example: nsrole / nsroledn / aci, etc. if ($type_id) { $uta = $this->object_type_attributes($object_name, $type_id); $attributes = array_merge( array_keys((array) $uta['auto_form_fields']), array_keys((array) $uta['form_fields']), array_keys((array) $uta['fields']) ); $attributes = array_filter($attributes); $attributes = array_unique($attributes); $object_attributes = array_keys($attrs); // extra attributes $extra_attrs = array_diff($attributes, $object_attributes); // remove attributes not listed in object type definition // @TODO: make this optional? $attributes = array_flip(array_merge($attributes, array($unique_attr))); $attrs = array_intersect_key($attrs, $attributes); } /* $auth = Auth::get_instance(); // Insert the persistent, unique attribute if (!array_key_exists($unique_attr, $attrs)) { $extra_attrs[] = $unique_attr; } // Get extra attributes if (!empty($extra_attrs)) { $extra_attrs = $auth->get_entry_attributes($dn, array_values($extra_attrs)); if (!empty($extra_attrs)) { $attrs = array_merge($attrs, $extra_attrs); } } */ // Replace unique attribute with 'id' key $attrs['id'] = $attrs[$unique_attr]; unset($attrs[$unique_attr]); // add object type id to the result $attrs['type_id'] = $type_id; // always return entrydn $attrs['entrydn'] = $dn; // add organizational unit to the result if (empty($attrs['ou']) && isset($attributes['ou'])) { $dn = kolab_utils::explode_dn($dn); // pop the rdn unset($dn[0]); $attrs['ou'] = implode(',', $dn); } return $attrs; } /** * Returns all supported attributes of specified object type * * @param string $object_name Name of the object (user, group, etc.) * * @return array Entry attributes */ public function object_attributes($object_name) { $unique_attr = self::unique_attribute(); $object_types = $this->object_types($object_name); $attributes = array(); // because we don't know the object type identifier before // we get it from LDAP we need to get try attributes of all types foreach ($object_types as $type) { $attributes = array_merge( $attributes, array_keys((array) $type['attributes']['auto_form_fields']), array_keys((array) $type['attributes']['form_fields']), array_keys((array) $type['attributes']['fields']) ); } // use array_values, because ldap_read() does not like an array // with removed elements (holes in the index) $attributes = array_values(array_unique($attributes)); if (empty($attributes)) { $attributes = array('*'); } // Insert the persistent, unique attribute if (!array_key_exists($unique_attr, $attributes)) { $attributes[] = $unique_attr; } return $attributes; } /** * Compare two score values * * @param string $s1 Score * @param string $s2 Score * * @return bool True when $s1 is greater than $s2 */ protected function score_compare($s1, $s2) { if (empty($s2) && !empty($s1)) { return true; } $s1 = explode(':', $s1); $s2 = explode(':', $s2); foreach ($s1 as $key => $val) { if ($val > $s2[$key]) { return true; } if ($val < $s2[$key]) { return false; } } return false; } /** * Returns name of unique attribute * * @return string Unique attribute name */ public static function unique_attribute() { $conf = Conf::get_instance(); $unique_attr = $conf->get('unique_attribute'); if (!$unique_attr) { $unique_attr = 'nsuniqueid'; } return $unique_attr; } /** * Returns unique attribute for specified entry DN * * @return string Unique attribute value */ protected function unique_attribute_value($dn) { // this method can be called internally quite often // let's cache results in memory if (!empty($this->cache['unique_attributes'][$dn])) { return $this->cache['unique_attributes'][$dn]; } $unique_attr = self::unique_attribute(); $auth = Auth::get_instance(); $result = $auth->get_entry_attribute($dn, $unique_attr); return $this->cache['unique_attributes'][$dn] = $result; } private function base_dn() { if (!empty($this->base_dn)) { return $this->base_dn; } // Get the domain information for expansion later $auth = Auth::get_instance(); $domain_info = $auth->domain_info($_SESSION['user']->get_domain()); $domain_info = empty($domain_info) ? null : $domain_info[key($domain_info)]; $dba = 'inetdomainbasedn'; $dna = $this->conf->get('domain_name_attribute'); if (empty($dna)) { $dna = 'associateddomain'; } $domain = $domain_info[$dna]; if (is_array($domain)) { $domain = $domain[0]; } if (empty($domain_info[$dba])) { $this->base_dn = 'dc=' . implode(',dc=', explode('.', $domain)); } else { $this->base_dn = $domain_info[$dba]; } return $this->base_dn; } } diff --git a/lib/locale/en_US.php b/lib/locale/en_US.php index 4340cf1..43e2b76 100644 --- a/lib/locale/en_US.php +++ b/lib/locale/en_US.php @@ -1,448 +1,448 @@ Kolab Server.'; $LANG['about.kolab'] = 'Kolab'; $LANG['about.kolabsys'] = 'Kolab Systems'; $LANG['about.support'] = 'Professional support is available from Kolab Systems.'; $LANG['about.technology'] = 'Technology'; $LANG['about.warranty'] = 'It comes with absolutely no warranties and is typically run entirely self supported. You can find help & information on the community web site.'; $LANG['aci.new'] = 'New...'; $LANG['aci.edit'] = 'Edit...'; $LANG['aci.remove'] = 'Remove'; $LANG['aci.users'] = 'Users'; $LANG['aci.rights'] = 'Rights'; $LANG['aci.targets'] = 'Targets'; $LANG['aci.aciname'] = 'ACI name:'; $LANG['aci.hosts'] = 'Hosts'; $LANG['aci.times'] = 'Times'; $LANG['aci.name'] = 'Name'; $LANG['aci.userid'] = 'User ID'; $LANG['aci.email'] = 'E-mail'; $LANG['aci.read'] = 'Read'; $LANG['aci.compare'] = 'Compare'; $LANG['aci.search'] = 'Search'; $LANG['aci.write'] = 'Write'; $LANG['aci.selfwrite'] = 'Self-write'; $LANG['aci.delete'] = 'Delete'; $LANG['aci.add'] = 'Add'; $LANG['aci.proxy'] = 'Proxy'; $LANG['aci.all'] = 'All rights'; $LANG['aci.allow'] = 'Allow'; $LANG['aci.deny'] = 'Deny'; $LANG['aci.typeusers'] = 'Users'; $LANG['aci.typegroups'] = 'Groups'; $LANG['aci.typeroles'] = 'Roles'; $LANG['aci.typeadmins'] = 'Administrators'; $LANG['aci.typespecials'] = 'Special Rights'; $LANG['aci.ldap-self'] = 'Self'; $LANG['aci.ldap-anyone'] = 'All users'; $LANG['aci.ldap-all'] = 'All authenticated users'; $LANG['aci.ldap-parent'] = 'Parent'; $LANG['aci.usersearch'] = 'Search for:'; $LANG['aci.usersearchresult'] = 'Search results:'; $LANG['aci.userselected'] = 'Selected users/groups/roles:'; $LANG['aci.useradd'] = 'Add'; $LANG['aci.userremove'] = 'Remove'; $LANG['aci.error.noname'] = 'ACI rule name is required!'; $LANG['aci.error.exists'] = 'ACI rule with specified name already exists!'; $LANG['aci.error.nousers'] = 'At least one user entry is required!'; $LANG['aci.rights.target'] = 'Target entry:'; $LANG['aci.rights.filter'] = 'Filter:'; $LANG['aci.rights.attrs'] = 'Attributes:'; $LANG['aci.checkall'] = 'Check all'; $LANG['aci.checknone'] = 'Check none'; $LANG['aci.thisentry'] = 'This entry'; $LANG['aci.selected'] = 'all selected'; $LANG['aci.other'] = 'all except selected'; $LANG['acl.all'] = 'All'; $LANG['acl.custom'] = 'Custom...'; $LANG['acl.full'] = 'Full (without access control)'; $LANG['acl.read-only'] = 'Read-Only'; $LANG['acl.read-write'] = 'Read/Write'; $LANG['acl.semi-full'] = 'Write new items'; $LANG['acl.l'] = 'l - Lookup'; $LANG['acl.r'] = 'r - Read messages'; $LANG['acl.s'] = 's - Keep Seen state'; $LANG['acl.w'] = 'w - Write flags'; $LANG['acl.i'] = 'i - Insert (Copy into)'; $LANG['acl.p'] = 'p - Post'; $LANG['acl.c'] = 'c - Create subfolders'; $LANG['acl.k'] = 'k - Create subfolders'; $LANG['acl.d'] = 'd - Delete messages'; $LANG['acl.t'] = 't - Delete messages'; $LANG['acl.e'] = 'e - Expunge'; $LANG['acl.x'] = 'x - Delete folder'; $LANG['acl.a'] = 'a - Administer'; $LANG['acl.n'] = 'n - Annotate messages'; $LANG['acl.identifier'] = 'Identifier'; $LANG['acl.rights'] = 'Access Rights'; $LANG['acl.expire'] = 'Expires On'; $LANG['acl.user'] = 'User...'; $LANG['acl.anyone'] = 'All users (anyone)'; $LANG['acl.anonymous'] = 'Guests (anonymous)'; $LANG['acl.error.invaliddate'] = 'Invalid date format!'; $LANG['acl.error.norights'] = 'No access rights specified!'; $LANG['acl.error.subjectexists'] = 'Access rights for specified identifier already exist!'; $LANG['acl.error.nouser'] = 'User identifier not specified!'; $LANG['add'] = 'Add'; $LANG['api.notypeid'] = 'No object type ID specified!'; $LANG['api.invalidtypeid'] = 'Invalid object type ID!'; $LANG['attribute.add'] = 'Add attribute'; $LANG['attribute.default'] = 'Default value'; $LANG['attribute.static'] = 'Static value'; $LANG['attribute.name'] = 'Attribute'; $LANG['attribute.optional'] = 'Optional'; $LANG['attribute.maxcount'] = 'Max. count'; $LANG['attribute.readonly'] = 'Read-only'; $LANG['attribute.type'] = 'Field type'; $LANG['attribute.value'] = 'Value'; $LANG['attribute.value.auto'] = 'Generated'; $LANG['attribute.value.auto-readonly'] = 'Generated (read-only)'; $LANG['attribute.value.normal'] = 'Normal'; $LANG['attribute.value.static'] = 'Static'; $LANG['attribute.options'] = 'Options'; $LANG['attribute.key.invalid'] = 'Type key contains forbidden characters!'; $LANG['attribute.required.error'] = 'Required attributes missing in attributes list ($1)!'; $LANG['attribute.validate'] = ' Validation'; $LANG['attribute.validate.default'] = 'default'; $LANG['attribute.validate.none'] = 'none'; $LANG['attribute.validate.basic'] = 'basic'; $LANG['attribute.validate.extended'] = 'extended'; $LANG['button.cancel'] = 'Cancel'; $LANG['button.clone'] = 'Clone'; $LANG['button.delete'] = 'Delete'; $LANG['button.ok'] = 'OK'; $LANG['button.save'] = 'Save'; $LANG['button.submit'] = 'Submit'; $LANG['creatorsname'] = 'Created by'; $LANG['days'] = 'days'; $LANG['debug'] = 'Debug info'; $LANG['delete'] = 'Delete'; $LANG['deleting'] = 'Deleting data...'; $LANG['domain.add'] = 'Add Domain'; $LANG['domain.add.success'] = 'Domain created successfully.'; $LANG['domain.associateddomain'] = 'Domain name(s)'; $LANG['domain.delete.confirm'] = 'Are you sure, you want to delete this domain?'; $LANG['domain.delete.force'] = "There are users assigned to this domain.\nAre you sure, you want to delete this domain and all assigned objects?"; $LANG['domain.delete.success'] = 'Domain deleted successfully.'; $LANG['domain.edit'] = 'Edit domain'; $LANG['domain.edit.success'] = 'Domain updated successfully.'; $LANG['domain.inetdomainbasedn'] = 'Custom Root DN'; $LANG['domain.inetdomainstatus'] = 'Status'; $LANG['domain.list'] = 'Domains List'; $LANG['domain.norecords'] = 'No domain records found!'; $LANG['domain.o'] = 'Organization'; $LANG['domain.other'] = 'Other'; $LANG['domain.system'] = 'System'; -$LANG['domain.type_id'] = 'Standard Domain'; +$LANG['domain.type_id'] = 'Domain type'; $LANG['edit'] = 'Edit'; $LANG['error'] = 'Error'; $LANG['error.401'] = 'Unauthorized.'; $LANG['error.403'] = 'Access forbidden.'; $LANG['error.404'] = 'Object not found.'; $LANG['error.408'] = 'Request timeout.'; $LANG['error.450'] = 'Domain is not empty.'; $LANG['error.500'] = 'Internal server error.'; $LANG['error.503'] = 'Service unavailable. Try again later.'; $LANG['form.required.empty'] = 'Some of the required fields are empty!'; $LANG['form.maxcount.exceeded'] = 'Maximum count of items exceeded!'; $LANG['group.add'] = 'Add Group'; $LANG['group.add.success'] = 'Group created successfully.'; $LANG['group.cn'] = 'Common name'; $LANG['group.delete.confirm'] = 'Are you sure, you want to delete this group?'; $LANG['group.delete.success'] = 'Group deleted successfully.'; $LANG['group.edit.success'] = 'Group updated successfully.'; $LANG['group.gidnumber'] = 'Primary group number'; $LANG['group.kolaballowsmtprecipient'] = 'Recipient(s) Access List'; $LANG['group.kolaballowsmtpsender'] = 'Sender Access List'; $LANG['group.list'] = 'Groups List'; $LANG['group.mail'] = 'Primary Email Address'; $LANG['group.member'] = 'Member(s)'; $LANG['group.memberurl'] = 'Members URL'; $LANG['group.norecords'] = 'No group records found!'; $LANG['group.other'] = 'Other'; $LANG['group.ou'] = 'Organizational Unit'; $LANG['group.system'] = 'System'; $LANG['group.type_id'] = 'Group type'; $LANG['group.uniquemember'] = 'Members'; $LANG['info'] = 'Information'; $LANG['internalerror'] = 'Internal system error!'; $LANG['ldap.one'] = 'one: all entries one level under the base DN'; $LANG['ldap.sub'] = 'sub: whole subtree starting with the base DN'; $LANG['ldap.base'] = 'base: base DN only'; $LANG['ldap.basedn'] = 'Base DN'; $LANG['ldap.host'] = 'LDAP Server'; $LANG['ldap.conditions'] = 'Conditions'; $LANG['ldap.scope'] = 'Scope'; $LANG['ldap.filter_any'] = 'is non-empty'; $LANG['ldap.filter_both'] = 'contains'; $LANG['ldap.filter_prefix'] = 'starts with'; $LANG['ldap.filter_suffix'] = 'ends with'; $LANG['ldap.filter_exact'] = 'is equal to'; $LANG['list.records'] = '$1 to $2 of $3'; $LANG['loading'] = 'Loading...'; $LANG['logout'] = 'Logout'; $LANG['login.username'] = 'Username'; $LANG['login.password'] = 'Password'; $LANG['login.login'] = 'Login'; $LANG['loginerror'] = 'Incorrect username or password!'; $LANG['MB'] = 'MB'; $LANG['menu.about'] = 'About'; $LANG['menu.domains'] = 'Domains'; $LANG['menu.groups'] = 'Groups'; $LANG['menu.ous'] = 'Units'; $LANG['menu.resources'] = 'Resources'; $LANG['menu.roles'] = 'Roles'; $LANG['menu.settings'] = 'Settings'; $LANG['menu.sharedfolders'] = 'Shared Folders'; $LANG['menu.users'] = 'Users'; $LANG['modifiersname'] = 'Modified by'; $LANG['password.generate'] = 'Generate password'; $LANG['reqtime'] = 'Request time: $1 sec.'; $LANG['ou.aci'] = 'Access Rights'; $LANG['ou.add'] = 'Add Unit'; $LANG['ou.add.success'] = 'Unit created successfully.'; $LANG['ou.ou'] = 'Unit Name'; $LANG['ou.delete.confirm'] = 'Are you sure, you want to delete this organizational unit?'; $LANG['ou.delete.success'] = 'Unit deleted successfully.'; $LANG['ou.description'] = 'Unit Description'; $LANG['ou.edit.success'] = 'Unit updated successfully.'; $LANG['ou.list'] = 'Organizational Units List'; $LANG['ou.norecords'] = 'No organizational unit records found!'; $LANG['ou.system'] = 'Details'; $LANG['ou.type_id'] = 'Unit Type'; $LANG['ou.base_dn'] = 'Parent Unit'; $LANG['resource.acl'] = 'Access Rights'; $LANG['resource.add'] = 'Add Resource'; $LANG['resource.add.success'] = 'Resource created successfully.'; $LANG['resource.cn'] = 'Name'; $LANG['resource.delete'] = 'Delete Resource'; $LANG['resource.delete.confirm'] = 'Are you sure, you want to delete this resource?'; $LANG['resource.delete.success'] = 'Resource deleted successfully.'; $LANG['resource.edit'] = 'Edit Resource'; $LANG['resource.edit.success'] = 'Resource updated successfully.'; $LANG['resource.kolabtargetfolder'] = 'Target Folder'; $LANG['resource.kolabinvitationpolicy'] = 'Invitation Policy'; $LANG['resource.kolabdescattribute'] = 'Attributes'; $LANG['resource.list'] = 'Resources (Collections) List'; $LANG['resource.mail'] = 'Mail Address'; $LANG['resource.member'] = 'Collection Members'; $LANG['resource.norecords'] = 'No resource records found!'; $LANG['resource.other'] = 'Other'; $LANG['resource.ou'] = 'Organizational Unit'; $LANG['resource.system'] = 'System'; $LANG['resource.type_id'] = 'Resource Type'; $LANG['resource.uniquemember'] = 'Collection Members'; $LANG['resource.description'] = 'Description'; $LANG['resource.owner'] = 'Owner'; $LANG['role.add'] = 'Add Role'; $LANG['role.add.success'] = 'Role created successfully.'; $LANG['role.cn'] = 'Role Name'; $LANG['role.delete.confirm'] = 'Are you sure, you want to delete this role?'; $LANG['role.delete.success'] = 'Role deleted successfully.'; $LANG['role.description'] = 'Role Description'; $LANG['role.edit.success'] = 'Role updated successfully.'; $LANG['role.list'] = 'Roles List'; $LANG['role.norecords'] = 'No role records found!'; $LANG['role.system'] = 'Details'; $LANG['role.type_id'] = 'Role Type'; $LANG['saving'] = 'Saving data...'; $LANG['search'] = 'Search...'; $LANG['search.reset'] = 'Reset'; $LANG['search.criteria'] = 'Search criteria'; $LANG['search.field'] = 'Field:'; $LANG['search.method'] = 'Method:'; $LANG['search.contains'] = 'contains'; $LANG['search.is'] = 'is'; $LANG['search.key'] = 'key'; $LANG['search.prefix'] = 'begins with'; $LANG['search.name'] = 'name'; $LANG['search.email'] = 'email'; $LANG['search.description'] = 'description'; $LANG['search.uid'] = 'UID'; $LANG['search.loading'] = 'Searching...'; $LANG['search.acchars'] = 'At least $min characters required for autocompletion'; $LANG['servererror'] = 'Server Error!'; $LANG['session.expired'] = 'Session has expired. Login again, please'; $LANG['sharedfolder.acl'] = 'IMAP Access Rights'; $LANG['sharedfolder.add'] = 'Add Shared Folder'; $LANG['sharedfolder.add.success'] = 'Shared folder created successfully.'; $LANG['sharedfolder.alias'] = 'Secondary Email Address(es)'; $LANG['sharedfolder.cn'] = 'Folder Name'; $LANG['sharedfolder.delete.confirm'] = 'Are you sure, you want to delete this shared folder?'; $LANG['sharedfolder.delete.success'] = 'Shared folder deleted successfully.'; $LANG['sharedfolder.edit'] = 'Edit Shared Folder'; $LANG['sharedfolder.edit.success'] = 'Shared folder updated successfully.'; $LANG['sharedfolder.kolaballowsmtprecipient'] = 'Recipient(s) Access List'; $LANG['sharedfolder.kolaballowsmtpsender'] = 'Sender Access List'; $LANG['sharedfolder.kolabdelegate'] = 'Delegate(s)'; $LANG['sharedfolder.kolabtargetfolder'] = 'Target IMAP Folder'; $LANG['sharedfolder.list'] = 'Shared Folders List'; $LANG['sharedfolder.norecords'] = 'No shared folder records found!'; $LANG['sharedfolder.mail'] = 'Email Address'; $LANG['sharedfolder.other'] = 'Other'; $LANG['sharedfolder.system'] = 'System'; $LANG['sharedfolder.type_id'] = 'Shared Folder Type'; $LANG['signup.headline'] = 'Sign Up for Hosted Kolab'; $LANG['signup.intro1'] = 'Having an account on a Kolab server is way better than just simple Email. It also provides you with full groupware functionality including synchronization for shared addressbooks, calendars, tasks, journal and more.'; $LANG['signup.intro2'] = 'You can sign up here now for an account.'; $LANG['signup.formtitle'] = 'Sign Up'; $LANG['signup.username'] = 'Username'; $LANG['signup.domain'] = 'Domain'; $LANG['signup.mailalternateaddress'] = 'Current Email Address'; $LANG['signup.futuremail'] = 'Future Email Address'; $LANG['signup.company'] = 'Company'; $LANG['signup.captcha'] = 'CAPTCHA'; $LANG['signup.userexists'] = 'User already exists!'; $LANG['signup.usercreated'] = '

Your account has been successfully added!

Congratulations, you now have your own Kolab account.'; $LANG['signup.wronguid'] = 'Invalid Username!'; $LANG['signup.wrongmailalternateaddress'] = 'Please provide a valid Email Address!'; $LANG['signup.footer'] = 'This is a service offered by Kolab Systems.'; $LANG['type.add'] = 'Add Object Type'; $LANG['type.add.success'] = 'Object type created successfully.'; $LANG['type.attributes'] = 'Attributes'; $LANG['type.description'] = 'Description'; $LANG['type.delete.confirm'] = 'Are you sure, you want to delete this object type?'; $LANG['type.delete.success'] = 'Object type deleted successfully.'; $LANG['type.domain'] = 'Domain'; $LANG['type.edit.success'] = 'Object type updated successfully.'; $LANG['type.group'] = 'Group'; $LANG['type.is_default'] = 'Default'; $LANG['type.list'] = 'Object Types List'; $LANG['type.key'] = 'Key'; $LANG['type.name'] = 'Name'; $LANG['type.norecords'] = 'No object type records found!'; $LANG['type.objectclass'] = 'Object class'; $LANG['type.object_type'] = 'Object type'; $LANG['type.ou'] = 'Organizational Unit'; $LANG['type.properties'] = 'Properties'; $LANG['type.resource'] = 'Resource'; $LANG['type.role'] = 'Role'; $LANG['type.sharedfolder'] = 'Shared Folder'; $LANG['type.used_for'] = 'Hosted'; $LANG['type.user'] = 'User'; $LANG['user.add'] = 'Add User'; $LANG['user.add.success'] = 'User created successfully.'; $LANG['user.alias'] = 'Secondary Email Address(es)'; $LANG['user.astaccountallowedcodec'] = 'Allowed codec(s)'; $LANG['user.astaccountcallerid'] = 'Caller ID'; $LANG['user.astaccountcontext'] = 'Account Context'; $LANG['user.astaccountdefaultuser'] = 'Asterisk Account Default User'; $LANG['user.astaccountdeny'] = 'Account deny'; $LANG['user.astaccounthost'] = 'Asterisk Host'; $LANG['user.astaccountmailbox'] = 'Mailbox'; $LANG['user.astaccountnat'] = 'Account uses NAT'; $LANG['user.astaccountname'] = 'Asterisk Account Name'; $LANG['user.astaccountqualify'] = 'Account Qualify'; $LANG['user.astaccountrealmedpassword'] = 'Realmed Account Password'; $LANG['user.astaccountregistrationexten'] = 'Extension'; $LANG['user.astaccountregistrationcontext'] = 'Registration Context'; $LANG['user.astaccountsecret'] = 'Plaintext Password'; $LANG['user.astaccounttype'] = 'Account Type'; $LANG['user.astcontext'] = 'Asterisk Context'; $LANG['user.asterisk'] = 'Asterisk SIP'; $LANG['user.astextension'] = 'Asterisk Extension'; $LANG['user.astvoicemailpassword'] = 'Voicemail PIN Code'; $LANG['user.c'] = 'Country'; $LANG['user.city'] = 'City'; $LANG['user.cn'] = 'Common name'; $LANG['user.config'] = 'Configuration'; $LANG['user.contact'] = 'Contact'; $LANG['user.contact_info'] = 'Contact Information'; $LANG['user.country'] = 'Country'; $LANG['user.country.desc'] = '2 letter code from ISO 3166-1'; $LANG['user.delete.confirm'] = 'Are you sure, you want to delete this user?'; $LANG['user.delete.success'] = 'User deleted successfully.'; $LANG['user.displayname'] = 'Display name'; $LANG['user.edit.success'] = 'User updated successfully.'; $LANG['user.fax'] = 'Fax number'; $LANG['user.fbinterval'] = 'Free-Busy interval'; $LANG['user.fbinterval.desc'] = 'Leave blank for default (60 days)'; $LANG['user.gidnumber'] = 'Primary group number'; $LANG['user.givenname'] = 'Given name'; $LANG['user.homedirectory'] = 'Home directory'; $LANG['user.homephone'] = 'Home Phone Number'; $LANG['user.initials'] = 'Initials'; $LANG['user.invitation-policy'] = 'Invitation policy'; $LANG['user.kolaballowsmtprecipient'] = 'Recipient(s) Access List'; $LANG['user.kolaballowsmtpsender'] = 'Sender Access List'; $LANG['user.kolabdelegate'] = 'Delegates'; $LANG['user.kolabhomeserver'] = 'Email Server'; $LANG['user.kolabinvitationpolicy'] = 'Invitation Handling Policy'; $LANG['user.l'] = 'City, Region'; $LANG['user.list'] = 'Users List'; $LANG['user.loginshell'] = 'Shell'; $LANG['user.mail'] = 'Primary Email Address'; $LANG['user.mailalternateaddress'] = 'External Email Address(es)'; $LANG['user.mailforwardingaddress'] = 'Forward Mail To'; $LANG['user.mailhost'] = 'Email Server'; $LANG['user.mailquota'] = 'Quota'; $LANG['user.mailquota.desc'] = 'Leave blank for unlimited'; $LANG['user.mobile'] = 'Mobile Phone Number'; $LANG['user.name'] = 'Name'; $LANG['user.norecords'] = 'No user records found!'; $LANG['user.nsrole'] = 'Role(s)'; $LANG['user.nsroledn'] = 'Role(s)'; $LANG['user.other'] = 'Other'; $LANG['user.o'] = 'Organization'; $LANG['user.org'] = 'Organization'; $LANG['user.orgunit'] = 'Organizational Unit'; $LANG['user.ou'] = 'Organizational Unit'; $LANG['user.pager'] = 'Pager Number'; $LANG['user.password.notallowed'] = 'Password contains characters (eg. Umlaut) that are not permitted'; $LANG['user.password.mismatch'] = 'Passwords do not match!'; $LANG['user.password.moreupper'] = 'Password needs to contain at least $1 uppercase character(s)'; $LANG['user.password.morelower'] = 'Password needs to contain at least $1 lowercase character(s)'; $LANG['user.password.moredigits'] = 'Password needs to contain at least $1 digit(s)'; $LANG['user.password.morespecial'] = 'Password needs to contain at least $1 special character(s): $2'; $LANG['user.password.tooshort'] = 'Password is too short, it must have at least $1 characters'; $LANG['user.personal'] = 'Personal'; $LANG['user.phone'] = 'Phone number'; $LANG['user.postalcode'] = 'Postal Code'; $LANG['user.postbox'] = 'Postal box'; $LANG['user.postcode'] = 'Postal code'; $LANG['user.preferredlanguage'] = 'Native tongue'; $LANG['user.room'] = 'Room number'; $LANG['user.sn'] = 'Surname'; $LANG['user.street'] = 'Street'; $LANG['user.system'] = 'System'; $LANG['user.telephonenumber'] = 'Phone Number'; $LANG['user.title'] = 'Job Title'; $LANG['user.type_id'] = 'Account type'; $LANG['user.uid'] = 'Unique identity (UID)'; $LANG['user.userpassword'] = 'Password'; $LANG['user.userpassword2'] = 'Confirm password'; $LANG['user.uidnumber'] = 'User ID number'; $LANG['welcome'] = 'Welcome to the Kolab Groupware Server Maintenance'; $LANG['yes'] = 'yes';