diff --git a/lib/Auth.php b/lib/Auth.php index 40f3049..701c385 100644 --- a/lib/Auth.php +++ b/lib/Auth.php @@ -234,6 +234,16 @@ class Auth { return $this->auth_instance()->domain_info($domaindata); } + public function domainadmin_get_domains($domainadmin) + { + return $this->auth_instance()->domainadmin_get_domains($domainadmin); + } + + public function domainadmin_get_configuration($domain, $variablename) + { + return $this->auth_instance()->domainadmin_get_configuration($domain, $variablename); + } + public function find_recipient($address) { return $this->auth_instance()->find_recipient($address); diff --git a/lib/Auth/LDAP.php b/lib/Auth/LDAP.php index 481deb9..3ac1f43 100644 --- a/lib/Auth/LDAP.php +++ b/lib/Auth/LDAP.php @@ -146,6 +146,22 @@ class LDAP extends Net_LDAP3 { $_SESSION['user']->user_bind_dn = $result; $_SESSION['user']->user_bind_pw = $password; + # if the user does not have access to the default domain, set another domain + $domains = $this->list_domains(); + $domain = ""; + foreach ($domains['list'] as $key => $value) { + $domain = $value['associateddomain']; + if (is_array($domain)) { + $domain = $domain[0]; + } + + if ($domain == $this->domain) { + break; + } + } + + $_SESSION['user']->set_domain($domain); + return $result; } @@ -175,6 +191,9 @@ class LDAP extends Net_LDAP3 { array_unshift($attributes[$domain_name_attribute], $domain); } + $attributes_domainadmin = $attributes["domainadmin"]; + unset($attributes["domainadmin"]); + $dn = $domain_name_attribute . '=' . $domain . ',' . $domain_base_dn; $result = $this->add_entry($dn, $attributes); @@ -466,9 +485,159 @@ class LDAP extends Net_LDAP3 { $this->add_entry($dn, $attrs); + $domain = $this->domain_info($domain, array_keys($attributes)); + + if (empty($domain)) { + return false; + } + + $domain_dn = key($domain); + + $this->domain_admin_save($domain, $domain_dn, $attributes_domainadmin); + + return true; + } + + private function ChangeDomainReadCapability($user, $domain, $action='add') + { + if (($tmpconn = ldap_connect($this->_ldap_server)) === false) { + return false; + } + + if (ldap_bind($tmpconn, $_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw) === false) { + ldap_close($tmpconn); + return false; + } + + $associateddomain_dn="associateddomain=$domain,cn=kolab,cn=config"; + $info = array(); + $info["aci"] = array(); + if (!(($sr = ldap_read($tmpconn, $associateddomain_dn, "(aci=*)", array('aci'))) === false)) { + $entry = ldap_get_entries($tmpconn, $sr); + if ($entry['count'] > 0) { + for ($count = 0; $count < $entry[0]['aci']['count']; $count++) { + if (strpos($entry[0]['aci'][$count], $user) === false) { + $info['aci'][] = $entry[0]['aci'][$count]; + } + } + } + } + + if ($action == 'add') { + $info["aci"][] = "(targetattr =\"*\")(version 3.0;acl \"$user\";allow (read,search) (userdn=\"ldap:///$user\");)"; + } + + if (ldap_modify($tmpconn, $associateddomain_dn, $info) === false) { + ldap_close($tmpconn); + return false; + } + + ldap_close($tmpconn); return true; } + private function domain_admin_save($domain, $domain_dn, $domainadmins) { + $currentdomain_dn = $this->_standard_root_dn($domain[$domain_dn]["associateddomain"]); + $currentdomain_da_dn = "cn=Directory Administrators,".$currentdomain_dn; + + $domain_admins_result = $this->_search($currentdomain_dn, "cn=Directory Administrators*", array("uniqueMember")); + if ($domain_admins_result != null && count($domain_admins_result) > 0) { + $domain_admins = $domain_admins_result->entries(true); + } + + if (empty($domain_admins[$currentdomain_da_dn]["uniquemember"])) { + $domain_admins[$currentdomain_da_dn]["uniquemember"] = Array(); + } + + if (!is_array($domain_admins[$currentdomain_da_dn]["uniquemember"])) { + $domain_admins[$currentdomain_da_dn]["uniquemember"] = + (array)($domain_admins[$currentdomain_da_dn]["uniquemember"]); + } + + if (empty($domainadmins)) { + $domainadmins = array(); + } + + if (!in_array('cn=Directory Manager', $domainadmins)) { + $domainadmins[] = 'cn=Directory Manager'; + } + + $info = array(); + $info["uniquemember"] = array(); + for ($count = 0; $count < count($domainadmins); $count++) { + $info["uniquemember"][] = $domainadmins[$count]; + + if (!in_array($domainadmins[$count], $domain_admins[$currentdomain_da_dn]["uniquemember"])) { + # add read permission to associateddomain in cn=kolab,cn=config + $this->ChangeDomainReadCapability($domainadmins[$count], $domain[$domain_dn]["associateddomain"], 'add'); + } + } + + # check for removed admins: remove also read permission from associateddomain in cn=kolab,cn=config + foreach ($domain_admins[$currentdomain_da_dn]["uniquemember"] as $oldadmin) { + if (!in_array($oldadmin, $domainadmins)) { + # drop read permission to associateddomain in cn=kolab,cn=config + $this->ChangeDomainReadCapability($oldadmin, $domain[$domain_dn]["associateddomain"], 'remove'); + } + } + + $result = $this->modify_entry($currentdomain_da_dn, $domain_admins[$currentdomain_da_dn], $info); + + if (!$result) { + return false; + } + } + + // returns an array with the domains that this domainadmin has access to + public function domainadmin_get_domains($domainadmin) + { + $filter = "(&(objectclass=domainrelatedobject)(aci=*".$domainadmin."*))"; + $domains_result = $this->_search($this->conf->get('domain_base_dn'), $filter, array("aci")); + if ($domains_result != null && count($domains_result) > 0) { + $domains = $domains_result->entries(true); + $result = array(); + foreach (array_keys($domains) as $associateddomain) { + $domain = substr($associateddomain, strlen("associateddomain=")); + $domain = substr($domain, 0, strpos($domain, ',')); + $result[] = $domain; + } + + return $result; + } + + return array(); + } + + // get the value of the specified configuration variable, for the domain admin of the currently selected domain + public function domainadmin_get_configuration($current_domain, $config_flag) + { + $current_domain = $this->_standard_root_dn($current_domain); + $domaindetails = array_shift(array_values($this->domain_info($current_domain, array('domainadmin')))); + $domainadmins = $domaindetails['domainadmin']; + + unset($resultvalue); + foreach ($domaindetails['domainadmin'] as $domainadmin) { + // ignore cn=Directory Manager + if ($domainadmin != $this->conf->get('bind_dn')) { + $userinfo = array_shift(array_values($this->user_info($domainadmin, array($config_flag)))); + if (isset($userinfo[$config_flag])) { + // what about multiple domain admins of one domain? + if (isset($resultvalue) && $resultvalue != $userinfo[$config_flag]) { + throw new Exception('error: domainadmins have contradicting settings for '.$config_flag); + } + $maindomainadmin = $domainadmin; + $resultvalue = $userinfo[$config_flag]; + } + } + } + + if (!isset($resultvalue)) { + return $resultvalue; + } + + return array('domainadmin' => $maindomainadmin, $config_flag => $resultvalue); + } + public function domain_edit($domain, $attributes, $typeid = null) { $domain = $this->domain_info($domain, array_keys($attributes)); @@ -479,6 +648,16 @@ class LDAP extends Net_LDAP3 { $domain_dn = key($domain); + # using isset, because if the array is empty, then we want to drop the domain admins. + if (isset($attributes["domainadmin"])) { + $this->domain_admin_save($domain, $domain_dn, $attributes["domainadmin"]); + unset($attributes["domainadmin"]); + } + + // unset the domainadmin and aci values in the original value, to avoid problems during ldap modify + unset($domain[$domain_dn]["domainadmin"]); + unset($domain[$domain_dn]["aci"]); + // We should start throwing stuff over the fence here. return $this->modify_entry($domain_dn, $domain[$domain_dn], $attributes); } @@ -582,6 +761,8 @@ class LDAP extends Net_LDAP3 { if ($result = $this->_search($domain_base_dn, $domain_filter, $attributes)) { $result = $result->entries(true); } + + $domain_dn = key($result); } else { $this->_log(LOG_DEBUG, "Auth::LDAP::domain_info() uses _read()"); @@ -592,6 +773,30 @@ class LDAP extends Net_LDAP3 { return false; } + if (isset($result[$domain_dn]["associateddomain"])) { + $currentdomain_dn = $this->_standard_root_dn($result[$domain_dn]["associateddomain"]); + } else { + $currentdomain_dn = $domain_dn; + } + + $currentdomain_da_dn = "cn=Directory Administrators,".$currentdomain_dn; + + $domain_admins_result = $this->_search($currentdomain_dn, "cn=Directory Administrators*", array("uniqueMember")); + if ($domain_admins_result != null && count($domain_admins_result) > 0) { + $domain_admins = $domain_admins_result->entries(true); + } + + // read domain admins from LDAP, uniqueMembers of Directory Administrators of domain + $result[$domain_dn]["domainadmin"] = array(); + if (is_array($domain_admins[$currentdomain_da_dn]["uniquemember"])) { + foreach ($domain_admins[$currentdomain_da_dn]["uniquemember"] as $domainadmin) { + $result[$domain_dn]["domainadmin"][] = $domainadmin; + } + } + else { + $result[$domain_dn]["domainadmin"][] = $domain_admins[$currentdomain_da_dn]["uniquemember"]; + } + $this->_log(LOG_DEBUG, "Auth::LDAP::domain_info() result: " . var_export($result, true)); return $result; diff --git a/lib/api/kolab_api_service_domain.php b/lib/api/kolab_api_service_domain.php index 4d5fcaa..da50b97 100644 --- a/lib/api/kolab_api_service_domain.php +++ b/lib/api/kolab_api_service_domain.php @@ -235,4 +235,25 @@ class kolab_api_service_domain extends kolab_api_service return false; } + + /** + * get some variables, specified in the TBits ISP LDAP attribute of the domain admin + * + * @param array $get GET parameters + * @param array $post POST parameters + * + * @return array|bool the value, False on error + */ + public function domainadmin_info($getdata, $postdata) + { + Log::trace("domain.domainadmin_info(\$getdata = '" . var_export($getdata, TRUE) . "', \$postdata = '" . var_export($postdata, TRUE) . "')"); + + if (empty($getdata['variablename'])) { + Log::error("domain.domainadmin_info called without a variable name"); + return false; + } + $variablename = $getdata['variablename']; + + return Auth::get_instance()->domainadmin_get_configuration($_SESSION['user']->get_domain(), $variablename); + } } diff --git a/lib/api/kolab_api_service_domain_types.php b/lib/api/kolab_api_service_domain_types.php index 6829047..5334112 100644 --- a/lib/api/kolab_api_service_domain_types.php +++ b/lib/api/kolab_api_service_domain_types.php @@ -34,6 +34,10 @@ class kolab_api_service_domain_types extends kolab_api_service 'associateddomain' => array( 'type' => 'list' ), + 'domainadmin' => array( + 'type' => 'list-domainadmins', + 'optional' => 'true', + ), 'inetdomainbasedn' => array( 'optional' => true, ), diff --git a/lib/api/kolab_api_service_form_value.php b/lib/api/kolab_api_service_form_value.php index 260a717..79a4720 100644 --- a/lib/api/kolab_api_service_form_value.php +++ b/lib/api/kolab_api_service_form_value.php @@ -1047,6 +1047,41 @@ class kolab_api_service_form_value extends kolab_api_service ); } + /** + * get the users that are domainadmins. + * We assume: all users in the domainadmin management domain (kolab.conf: domainadmins_management_domain), plus the Directory Manager + */ + private function select_options_domainadmin($postdata, $attribs = array()) + { + $conf = Conf::get_instance(); + + $domainadmin_dn = "dc=" . implode(',dc=', explode('.', $conf->get("domainadmins_management_domain"))); + $auth = Auth::get_instance($domainadmin_dn); + + $_domainadmins = array(); + + $domainadmin = array(); + $domainadmin[] = $conf->get("bind_dn"); // "cn=Directory Manager"; + $domainadmin[] = "Directory Manager"; + $_domainadmins[] = $domainadmin; + + if ($domainadmins = $auth->search($domainadmin_dn, '(objectclass=kolabinetorgperson)')) { + foreach ($domainadmins->entries(true) as $domainadmin_dn => $domainadmin_attrs) { + $domainadmin = array(); + $domainadmin[] = $domainadmin_dn; + $domainadmin[] = $domainadmin_attrs['displayname']; + $_domainadmins[] = $domainadmin; + } + + sort($_domainadmins); + } + + return array( + 'list' => $_domainadmins, + 'default' => strtolower($conf->get("bind_dn")), + ); + } + private function select_options_preferredlanguage($postdata, $attribs = array()) { $options = $this->_select_options_from_db('preferredlanguage'); @@ -1187,7 +1222,7 @@ class kolab_api_service_form_value extends kolab_api_service return 'OK'; } - private function validate_mailquota($value, $postdata = array(), $validation_type = null) + private function validate_quota($value, $postdata = array(), $validation_type = null) { // convert MB/GB into KB if (preg_match('/^([0-9]+)\s*(KB|MB|GB)$/i', $value, $m)) { @@ -1197,10 +1232,24 @@ class kolab_api_service_form_value extends kolab_api_service case 'GB': $value = $m[1] * 1024 * 1024; break; } } - return (string) intval($value); } + private function validate_mailquota($value, $postdata = array(), $validation_type = null) + { + return $this->validate_quota($value, $postdata, $validation_type); + } + + private function validate_tbitskolaboverallquota($value, $postdata = array(), $validation_type = null) + { + return $this->validate_quota($value, $postdata, $validation_type); + } + + private function validate_tbitskolabdefaultquota($value, $postdata = array(), $validation_type = null) + { + return $this->validate_quota($value, $postdata, $validation_type); + } + private function validate_mailalternateaddress($value, $postdata = array(), $validation_type = null) { $conf = Conf::get_instance(); @@ -1456,6 +1505,19 @@ class kolab_api_service_form_value extends kolab_api_service if (in_array($email_domain, $valid_domains)) { $valid = true; } + + if (!$valid) { + // check if domain of the email address is maintained by the same domain admin as the currently selected domain + // get the domain admin of the currently selected domain + $result = $auth->domainadmin_get_configuration($_SESSION['user']->get_domain(), 'uid'); + + if (isset($result)) { + $valid_domains = $auth->domainadmin_get_domains($result['domainadmin']); + if (in_array($email_domain, $valid_domains)) { + $valid = true; + } + } + } if ($valid) { Log::trace("Found email address to be in one of my domains."); diff --git a/lib/client/kolab_client_task_domain.php b/lib/client/kolab_client_task_domain.php index 4a1190a..25d601a 100644 --- a/lib/client/kolab_client_task_domain.php +++ b/lib/client/kolab_client_task_domain.php @@ -115,6 +115,7 @@ class kolab_client_task_domain extends kolab_client_task $sections = array( 'system' => 'domain.system', 'other' => 'domain.other', + 'admins' => 'domain.admins', ); // field-to-section map and fields order @@ -122,6 +123,7 @@ class kolab_client_task_domain extends kolab_client_task 'type_id' => 'system', 'type_id_name' => 'system', 'associateddomain' => 'system', + 'domainadmin' => 'admins', ); //console("domain_form() \$data", $data); @@ -177,6 +179,15 @@ class kolab_client_task_domain extends kolab_client_task ); } + // load all domain admins, ie. all users from the default domain + $param = array(); + $param['attributes'] = array('domainadmin'); + $resp = $this->api_post('form_value.select_options', null, $param); + $resp = $resp->get('domainadmin'); + + $default = $resp['default']; + $data['domainadmin_options'] = $resp['list']; + // Create form object and populate with fields $form = $this->form_create('domain', $attribs, $sections, $fields, $fields_map, $data, $add_mode); diff --git a/lib/client/kolab_client_task_settings.php b/lib/client/kolab_client_task_settings.php index f1e37ac..7a7c481 100644 --- a/lib/client/kolab_client_task_settings.php +++ b/lib/client/kolab_client_task_settings.php @@ -34,6 +34,7 @@ class kolab_client_task_settings extends kolab_client_task protected $form_element_types = array( 'text', 'select', 'multiselect', 'list', 'list-autocomplete', 'checkbox', 'password', 'ldap_url', 'text-quota', + 'list-domainadmins', ); diff --git a/lib/client/kolab_client_task_user.php b/lib/client/kolab_client_task_user.php index 40c2fcc..0100a83 100644 --- a/lib/client/kolab_client_task_user.php +++ b/lib/client/kolab_client_task_user.php @@ -88,6 +88,7 @@ class kolab_client_task_user extends kolab_client_task 'contact_info' => 'user.contact_info', 'system' => 'user.system', 'config' => 'user.config', + 'domainadmin' => 'user.domainadmin', 'asterisk' => 'user.asterisk', 'other' => 'user.other', ); @@ -143,6 +144,10 @@ class kolab_client_task_user extends kolab_client_task 'kolabhomeserver' => 'config', 'mailhost' => 'config', 'mailquota' => 'config', + 'tbitskolabmaxaccounts' => 'domainadmin', + 'tbitskolaballowgroupware' => 'domainadmin', + 'tbitskolaboverallquota' => 'domainadmin', + 'tbitskolabdefaultquota' => 'domainadmin', 'cyrususerquota' => 'config', 'kolabfreebusyfuture' => 'config', 'kolabinvitationpolicy' => 'config', diff --git a/lib/kolab_api_service.php b/lib/kolab_api_service.php index 1972cb0..4fef41b 100644 --- a/lib/kolab_api_service.php +++ b/lib/kolab_api_service.php @@ -75,6 +75,7 @@ abstract class kolab_api_service 'attributes' => kolab_api_service_domain_types::$DEFAULT_TYPE_ATTRS, ), ); + $object_types['1']['attributes']['form_fields']['domainadmin']['type'] = 'list'; $object_types['1']['attributes']['form_fields']['aci'] = array( 'type' => 'list', 'optional' => true, @@ -125,6 +126,10 @@ abstract class kolab_api_service return key($object_types); } + # sort object types by the key: Kolab user should win over Domain Admin, because it comes first + # there are only additional fields, so a kolab user would be displayed as admin, when sorting by type name + ksort($object_types); + $object_class = array_map('strtolower', $object_class); $object_keys = array_keys($attributes); $keys_count = count($object_keys); diff --git a/lib/kolab_client_task.php b/lib/kolab_client_task.php index c845126..7d96232 100644 --- a/lib/kolab_client_task.php +++ b/lib/kolab_client_task.php @@ -645,6 +645,24 @@ class kolab_client_task } /** + * Returns the id of the type that is identified by the key + * + * @param string $types array of types for the current object class + * @param string $key identifies the type that we want the id for + * + * @return string the id for the type that we are looking for + */ + protected function get_object_type_id_by_key($types, $key) + { + foreach ($types as $id => $data) { + if ($data['key'] == $key) { + return $id; + } + } + return -1; + } + + /** * Returns user name. * * @param string $dn User DN attribute value @@ -879,6 +897,11 @@ class kolab_client_task $result['default'] = $field['default']; break; + case 'list-domainadmins': + $result['type'] = kolab_form::INPUT_DOMAINADMIN; + $result['data-type'] = kolab_form::TYPE_LIST; + break; + default: $result['type'] = kolab_form::INPUT_TEXT; @@ -979,6 +1002,17 @@ class kolab_client_task if (!empty($data['type_id'])) { $type = $data['type_id']; } + else if ($name == "user") { + // set the default user type + $defaultUserType = $this->get_object_type_id_by_key($types, "kolab"); + $conf = Conf::get_instance(); + if ($conf->get('domainadmins_management_domain') == $_SESSION['user']['domain']) { + // in the management domain, default to DomainAdmin user + $defaultUserType = $this->get_object_type_id_by_key($types, "domainadmin"); + } + + $data['type_id'] = $type = $defaultUserType; + } else { $data['type_id'] = $type = key($types); } @@ -1283,6 +1317,12 @@ class kolab_client_task unset($field['default']); } + // used for selectlist, eg. domainadmins + if (!empty($data[$idx."_options"])) + { + $assoc_fields[$idx] = !empty($data[$idx."_options"]) ? $data[$idx."_options"] : array(); + } + // @TODO: We assume here that all autocompletion lists are associative // It's likely that we'll need autocompletion on ordinary lists if (!empty($field['data-autocomplete'])) { diff --git a/lib/kolab_form.php b/lib/kolab_form.php index 2fe3e9c..125dab5 100644 --- a/lib/kolab_form.php +++ b/lib/kolab_form.php @@ -39,6 +39,7 @@ class kolab_form const INPUT_CUSTOM = 10; const INPUT_CONTENT = 20; const INPUT_TEXTQUOTA = 30; + const INPUT_DOMAINADMIN = 40; const TYPE_LIST = 1; @@ -314,6 +315,11 @@ class kolab_form $content = kolab_html::textarea($attribs, true); break; + case self::INPUT_DOMAINADMIN: + $attribs['data-type'] = 'selectlist'; + $content = kolab_html::textarea($attribs, true); + break; + case self::INPUT_SELECT: if (!empty($attribs['multiple']) && empty($attribs['size'])) { $attribs['size'] = 5; diff --git a/lib/locale/de_DE.php b/lib/locale/de_DE.php index b88e520..607f36a 100644 --- a/lib/locale/de_DE.php +++ b/lib/locale/de_DE.php @@ -31,8 +31,10 @@ $LANG['delete'] = 'Löschen'; $LANG['deleting'] = 'Daten löschen…'; $LANG['domain.add'] = 'Domäne hinzufügen'; $LANG['domain.add.success'] = 'Domäne erfolgreich hinzugefügt.'; +$LANG['domain.admins'] = 'Domain Administratoren'; $LANG['domain.associateddomain'] = 'Domänenname(n)'; $LANG['domain.delete.success'] = 'Domäne erfolgreich gelöscht.'; +$LANG['domain.domainadmin'] = 'Administratoren für diese Domain'; $LANG['domain.edit'] = 'Domäne bearbeiten'; $LANG['domain.edit.success'] = 'Domäne erfolgreich aktualisiert.'; $LANG['domain.inetdomainbasedn'] = 'Benutzerdefinierte(s) Root DN(s)'; @@ -192,6 +194,7 @@ $LANG['user.country'] = 'Land'; $LANG['user.country.desc'] = '2 Buchstaben Ländercode aus ISO 3166-1'; $LANG['user.delete.success'] = 'Benutzer erfolgreich gelöscht.'; $LANG['user.displayname'] = 'Anzeigename'; +$LANG['user.domainadmin'] = 'Domain Administrator'; $LANG['user.edit.success'] = 'Benutzer erfolgreich bearbeitet.'; $LANG['user.fax'] = 'Faxnummer'; $LANG['user.fbinterval'] = 'Frei-Beschäftigt Intervall'; @@ -238,6 +241,10 @@ $LANG['user.sn'] = 'Nachname'; $LANG['user.street'] = 'Straße'; $LANG['user.system'] = 'System'; $LANG['user.telephonenumber'] = 'Telefonnummer'; +$LANG['user.tbitskolaballowgroupware'] = 'Erlaube Groupware Funktionen für Benutzer'; +$LANG['user.tbitskolabmaxaccounts'] = 'Maximale Anzahl von Benutzerkonten'; +$LANG['user.tbitskolaboverallquota'] = 'Gesamtquota verfügbar'; +$LANG['user.tbitskolabdefaultquota'] = 'Voreinstellung Quota für Benutzerkonten'; $LANG['user.title'] = 'Jobbezeichnung'; $LANG['user.type_id'] = 'Kontotyp'; $LANG['user.uid'] = 'Eindeutige Identität (UID)'; diff --git a/lib/locale/en_US.php b/lib/locale/en_US.php index bc42754..339cbc7 100644 --- a/lib/locale/en_US.php +++ b/lib/locale/en_US.php @@ -47,9 +47,11 @@ $LANG['deleting'] = 'Deleting data...'; $LANG['domain.add'] = 'Add Domain'; $LANG['domain.add.success'] = 'Domain created successfully.'; +$LANG['domain.admins'] = 'Domain Administrators'; $LANG['domain.associateddomain'] = 'Domain name(s)'; $LANG['domain.delete.confirm'] = 'Are you sure, you want to delete this domain?'; $LANG['domain.delete.success'] = 'Domain deleted successfully.'; +$LANG['domain.domainadmin'] = 'Administrators for this domain'; $LANG['domain.edit'] = 'Edit domain'; $LANG['domain.edit.success'] = 'Domain updated successfully.'; $LANG['domain.inetdomainbasedn'] = 'Custom Root DN(s)'; @@ -268,6 +270,7 @@ $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.domainadmin'] = 'Domain Administrator'; $LANG['user.edit.success'] = 'User updated successfully.'; $LANG['user.fax'] = 'Fax number'; $LANG['user.fbinterval'] = 'Free-Busy interval'; @@ -315,6 +318,10 @@ $LANG['user.sn'] = 'Surname'; $LANG['user.street'] = 'Street'; $LANG['user.system'] = 'System'; $LANG['user.telephonenumber'] = 'Phone Number'; +$LANG['user.tbitskolaballowgroupware'] = 'Allow Groupware features for accounts'; +$LANG['user.tbitskolabmaxaccounts'] = 'Maximum number of accounts'; +$LANG['user.tbitskolaboverallquota'] = 'Overall Quota assigned'; +$LANG['user.tbitskolabdefaultquota'] = 'Default Quota for user accounts'; $LANG['user.title'] = 'Job Title'; $LANG['user.type_id'] = 'Account type'; $LANG['user.uid'] = 'Unique identity (UID)'; diff --git a/public_html/js/kolab_admin.js b/public_html/js/kolab_admin.js index 4df6339..49a01a9 100644 --- a/public_html/js/kolab_admin.js +++ b/public_html/js/kolab_admin.js @@ -715,6 +715,8 @@ function kolab_admin() // replace some textarea fields with pretty/smart input lists $('textarea[data-type="list"]', form) .each(function() { kadm.form_list_element_wrapper(this); }); + $('textarea[data-type="selectlist"]', form) + .each(function() { kadm.form_list_element_wrapper(this, "select"); }); // create smart select fields $('input[data-type="select"]', form) .each(function() { kadm.form_select_element_wrapper(this); }); @@ -751,6 +753,22 @@ function kolab_admin() data.json[this.name] = value; }); + // list of selects + $('textarea[data-type="selectlist"]:not(:disabled)', form).each(function() { + var i, v, value = [], + re = RegExp('^' + RegExp.escape(this.name) + '\[[0-9-]+\]$'); + + for (i in data.json) { + if (i.match(re)) { + if (v = $('select[name="'+i+'"]', form).val()) + value.push(v); + delete data.json[i]; + } + } + + data.json[this.name] = value; + }); + // smart selects $('input[data-type="select"]', form).each(function() { delete data.json[this.name]; @@ -794,8 +812,10 @@ function kolab_admin() }; // Replaces form element with smart list element - this.form_list_element_wrapper = function(form_element) + this.form_list_element_wrapper = function(form_element, element_type) { + element_type = element_type || "text"; + var i = 0, j = 0, list = [], elem, e = $(form_element), form = form_element.form, disabled = e.attr('disabled'), @@ -861,14 +881,24 @@ function kolab_admin() // add input rows $.each(list, function(i, v) { - var elem = kadm.form_list_element(form, { - value: v, - key: i, - maxlength: maxlength, - autocomplete: autocomplete, - element: e - }, j++); - + if (element_type == 'text') { + var elem = kadm.form_list_element(form, { + value: v, + key: i, + maxlength: maxlength, + autocomplete: autocomplete, + element: e + }, j++); + } + else if (element_type='select') { + var elem = kadm.form_selectlist_element(form, { + value: v, + key: i, + maxlength: maxlength, + autocomplete: autocomplete, + element: e + }, j++); + } elem.appendTo(area); }); } @@ -953,6 +983,90 @@ function kolab_admin() return elem; }; + // Creates smart list element + this.form_selectlist_element = function(form, data, idx) + { + var content, elem, input, + key = data.key, + orig = data.element, + ac = data.autocomplete; + + assoc_fields_options = (orig ? orig.attr('name') : data.name); // + '_options'; + data.name = (orig ? orig.attr('name') : data.name) + '[' + idx + ']'; + data.readonly = (ac && idx >= 0); + + // remove internal attributes + delete data['element']; + delete data['autocomplete']; + delete data['key']; + + // build element content + content = '' + + (!ac ? '' : ac && idx == -1 ? '' : '') + + (!ac || idx >= 0 ? '' : '') + + ''; + + elem = $(content); + input = $('select', elem); + + // Set INPUT attributes + input.attr(data); + + if (data.readonly) + input.addClass('readonly'); + if (ac) + input.addClass('autocomplete'); + + // attach element creation event + if (!ac) + $('span[class="add"]', elem).click(function() { + var name = data.name.replace(/\[[0-9]+\]$/, ''), + span = $(this.parentNode.parentNode), + maxcount = $('textarea[name="'+name+'"]').attr('data-maxcount'); + + // check element count limit + if (maxcount && maxcount <= span.parent().children().length) { + alert(kadm.t('form.maxcount.exceeded')); + return; + } + + var dt = (new Date()).getTime(), + elem = kadm.form_selectlist_element(form, {name: name}, dt); + + kadm.ac_stop(); + span.after(elem); + $('select', elem).focus(); + }); + + // attach element deletion event + if (!ac || idx >= 0) + $('span[class="reset"]', elem).click(function() { + var span = $(this.parentNode.parentNode), + name = data.name.replace(/\[[0-9]+\]$/, ''), + l = $('select[name^="' + name + '"]', form), + key = $(this).data('key'); + + if (l.length > 1 || $('select[name="' + name + '"]', form).attr('data-autocomplete')) + span.remove(); + else + $('select', span).val('').focus(); + + // delete key from internal field representation + if (key !== undefined && kadm.env.assoc_fields[name]) + delete kadm.env.assoc_fields[name][key]; + + kadm.ac_stop(); + }).data('key', key); + + return elem; + }; + this.form_element_oninsert = function(key, val) { var elem, input = $(this.ac_input).get(0), diff --git a/public_html/skins/default/style.css b/public_html/skins/default/style.css index 6647caa..b439cca 100644 --- a/public_html/skins/default/style.css +++ b/public_html/skins/default/style.css @@ -631,6 +631,11 @@ span.listelement input:focus { outline: none; } +span.listelement select { + width: 332px; + height: 18px; +} + span.listelement span.actions { float: left; padding: 1px 0;