diff options
author | Aleksander Machniak <machniak@kolabsys.com> | 2014-03-21 16:05:35 +0100 |
---|---|---|
committer | Aleksander Machniak <machniak@kolabsys.com> | 2014-03-21 16:06:58 +0100 |
commit | 5a62c7e3594c60b7f37c417d5a004010ffb8c767 (patch) | |
tree | 020dc86e360f66ba977983a938387b5dbbd82efb | |
parent | 26afc433416d6fcec4ca35f48d516f7c9330e949 (diff) | |
download | webadmin-5a62c7e3594c60b7f37c417d5a004010ffb8c767.tar.gz |
Add ACI form widget (Request #1782)
-rw-r--r-- | lib/Auth/LDAP.php | 3 | ||||
-rw-r--r-- | lib/api/kolab_api_service_form_value.php | 16 | ||||
-rw-r--r-- | lib/api/kolab_api_service_ou.php | 4 | ||||
-rw-r--r-- | lib/api/kolab_api_service_resource.php | 2 | ||||
-rw-r--r-- | lib/api/kolab_api_service_sharedfolder.php | 2 | ||||
-rw-r--r-- | lib/api/kolab_api_service_user.php | 8 | ||||
-rw-r--r-- | lib/client/kolab_client_task_ou.php | 21 | ||||
-rw-r--r-- | lib/client/kolab_client_task_settings.php | 4 | ||||
-rw-r--r-- | lib/ext/Net/LDAP3.php | 2 | ||||
-rw-r--r-- | lib/kolab_api_service.php | 71 | ||||
-rw-r--r-- | lib/kolab_client_task.php | 23 | ||||
-rw-r--r-- | lib/kolab_form.php | 12 | ||||
-rw-r--r-- | lib/locale/en_US.php | 51 | ||||
-rw-r--r-- | public_html/js/kolab_admin.js | 1124 | ||||
-rw-r--r-- | public_html/skins/default/style.css | 171 | ||||
-rw-r--r-- | public_html/skins/default/ui.js | 6 |
16 files changed, 1316 insertions, 204 deletions
diff --git a/lib/Auth/LDAP.php b/lib/Auth/LDAP.php index 83eb859..bfba321 100644 --- a/lib/Auth/LDAP.php +++ b/lib/Auth/LDAP.php @@ -1026,6 +1026,7 @@ class LDAP extends Net_LDAP3 { // additional special attributes that aren't in LDAP schema $additional_attributes = array( 'top' => array('nsRoleDN'), + '*' => array('aci'), ); if (!empty($attributes)) { @@ -1034,6 +1035,8 @@ class LDAP extends Net_LDAP3 { $attributes['may'] = array_merge($attributes['may'], $attrs); } } + + $attributes['may'] = array_merge($attributes['may'], $additional_attributes['*']); } return $attributes; diff --git a/lib/api/kolab_api_service_form_value.php b/lib/api/kolab_api_service_form_value.php index fec6372..58b30a1 100644 --- a/lib/api/kolab_api_service_form_value.php +++ b/lib/api/kolab_api_service_form_value.php @@ -950,6 +950,22 @@ class kolab_api_service_form_value extends kolab_api_service private function select_options_attribute($postdata, $attribs = array()) { + // if objectClasses aren't specified we'll use all classes already in use + // not all classes in LDAP + if (empty($postdata['classes'])) { + $postdata['classes'] = array(); + + foreach ($this->supported_types as $type) { + foreach ($this->object_types($type) as $obj_type) { + if ($obj_type['attributes'] && $obj_type['attributes']['fields']) { + $postdata['classes'] = array_merge($postdata['classes'], (array) $obj_type['attributes']['fields']['objectclass']); + } + } + } + } + + $postdata['classes'] = array_unique($postdata['classes']); + $auth = Auth::get_instance(); $list = $auth->schema_attributes($postdata['classes']); diff --git a/lib/api/kolab_api_service_ou.php b/lib/api/kolab_api_service_ou.php index 7e9eddc..b7c902e 100644 --- a/lib/api/kolab_api_service_ou.php +++ b/lib/api/kolab_api_service_ou.php @@ -167,11 +167,11 @@ class kolab_api_service_ou extends kolab_api_service $result = $auth->organizationalunit_info($getdata['id']); // normalize result - $result = $this->parse_result_attributes('ou', $result, $dn); + $result = $this->parse_result_attributes('ou', $result); if ($result) { // get base_dn "attribute" for the API client - $dn = substr($dn, strlen($result['ou']) + 4); + $dn = substr($result['entrydn'], strlen($result['ou']) + 4); if (strpos($dn, 'ou=') === 0) { $result['base_dn'] = $dn; } diff --git a/lib/api/kolab_api_service_resource.php b/lib/api/kolab_api_service_resource.php index ac04f3b..f5d095f 100644 --- a/lib/api/kolab_api_service_resource.php +++ b/lib/api/kolab_api_service_resource.php @@ -165,8 +165,6 @@ class kolab_api_service_resource extends kolab_api_service // normalize result $result = $this->parse_result_attributes('resource', $result); - //console($result); - if ($result) { return $result; } diff --git a/lib/api/kolab_api_service_sharedfolder.php b/lib/api/kolab_api_service_sharedfolder.php index 23518b3..d14c4fc 100644 --- a/lib/api/kolab_api_service_sharedfolder.php +++ b/lib/api/kolab_api_service_sharedfolder.php @@ -165,8 +165,6 @@ class kolab_api_service_sharedfolder extends kolab_api_service // normalize result $result = $this->parse_result_attributes('sharedfolder', $result); - //console($result); - if ($result) { return $result; } diff --git a/lib/api/kolab_api_service_user.php b/lib/api/kolab_api_service_user.php index 6cf4483..acdaec0 100644 --- a/lib/api/kolab_api_service_user.php +++ b/lib/api/kolab_api_service_user.php @@ -178,11 +178,11 @@ class kolab_api_service_user extends kolab_api_service $result = $this->parse_result_attributes('user', $result); if (empty($result['ou'])) { - $_dn = ldap_explode_dn($result_dn, 0); + $dn = ldap_explode_dn($result['entrydn'], 0); // pop the count and rdn - unset($_dn['count']); - unset($_dn[0]); - $result['ou'] = implode(',', $_dn); + unset($dn['count']); + unset($dn[0]); + $result['ou'] = implode(',', $dn); } Log::trace("user.info on " . $getdata['id'] . " parsed result: " . var_export($result, TRUE)); diff --git a/lib/client/kolab_client_task_ou.php b/lib/client/kolab_client_task_ou.php index e1d80a8..1cddeff 100644 --- a/lib/client/kolab_client_task_ou.php +++ b/lib/client/kolab_client_task_ou.php @@ -70,10 +70,10 @@ class kolab_client_task_ou extends kolab_client_task */ public function action_info() { - $id = $this->get_input('id', 'POST'); - $result = $this->api_get('ou.info', array('id' => $id)); - $resource = $result->get(); - $output = $this->ou_form(null, $resource); + $id = $this->get_input('id', 'POST'); + $result = $this->api_get('ou.info', array('id' => $id)); + $unit = $result->get(); + $output = $this->ou_form(null, $unit); $this->output->set_object('taskcontent', $output); } @@ -136,16 +136,19 @@ class kolab_client_task_ou extends kolab_client_task // Form sections $sections = array( 'system' => 'ou.system', + 'aci' => 'ou.aci', 'other' => 'ou.other', ); // field-to-section map and fields order $fields_map = array( - 'type_id' => 'system', - 'type_id_name' => 'system', - 'ou' => 'system', - 'base_dn' => 'system', - 'description' => 'system', + 'type_id' => 'system', + 'type_id_name' => 'system', + 'ou' => 'system', + 'base_dn' => 'system', + 'description' => 'system', + + 'aci' => 'aci', ); // Prepare fields diff --git a/lib/client/kolab_client_task_settings.php b/lib/client/kolab_client_task_settings.php index f1e37ac..56feb97 100644 --- a/lib/client/kolab_client_task_settings.php +++ b/lib/client/kolab_client_task_settings.php @@ -33,7 +33,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', + 'text-quota', 'aci', ); @@ -763,7 +763,7 @@ class kolab_client_task_settings extends kolab_client_task ), 'options' => array( 'type' => kolab_form::INPUT_TEXTAREA, - 'data-type' => kolab_form::TYPE_LIST, + 'data-type' => 'list', ), 'maxcount' => array( 'type' => kolab_form::INPUT_TEXT, diff --git a/lib/ext/Net/LDAP3.php b/lib/ext/Net/LDAP3.php index 674f261..550cebd 100644 --- a/lib/ext/Net/LDAP3.php +++ b/lib/ext/Net/LDAP3.php @@ -1441,7 +1441,7 @@ class Net_LDAP3 } } // not OU object, but changed ou attribute - else if ((!empty($old_ou) || !empty($new_ou)) && strtolower($old_ou) !== strtolower($new_ou)) { + else if ((!empty($old_ou) && !empty($new_ou)) && strtolower($old_ou) !== strtolower($new_ou)) { $mod_array['rename']['new_parent'] = $new_ou; if (empty($mod_array['rename']['dn']) || empty($mod_array['rename']['new_rdn'])) { $mod_array['rename']['dn'] = $subject_dn; diff --git a/lib/kolab_api_service.php b/lib/kolab_api_service.php index 4733347..752c243 100644 --- a/lib/kolab_api_service.php +++ b/lib/kolab_api_service.php @@ -67,22 +67,7 @@ abstract class kolab_api_service return array(); } - // get list of object types - if ($object_name == 'domain') { - $object_types = array( - '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 { - $object_types = $this->object_types($object_name); - } + $object_types = $this->object_types($object_name); if (empty($type_id)) { if (count($object_types) == 1) { @@ -224,7 +209,7 @@ abstract class kolab_api_service */ protected function object_types($object_name) { - if (!$object_name || !in_array($object_name, $this->supported_types_db)) { + if (!$object_name || !in_array($object_name, $this->supported_types)) { return array(); } @@ -238,29 +223,43 @@ abstract class kolab_api_service } } - $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; + // get list of object types + if ($object_name == 'domain') { + $object_types = array( + '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; + } } } } } - //console("Object types for " . $object_name, $object_types); - if ($devel_mode == null) { return $this->cache['object_types'][$object_name] = $object_types; - } else { + } + else { return $object_types; } @@ -458,11 +457,10 @@ abstract class kolab_api_service * * @param string $object_name Name of the object (user, group, etc.) * @param array $attrs Entry attributes - * @param string $dn Will be filled with object base DN * * @return array Entry attributes */ - protected function parse_result_attributes($object_name, $attrs = array(), &$dn = null) + protected function parse_result_attributes($object_name, $attrs = array()) { //console("parse_result_attributes($object_name, \$attrs = ", $attrs); @@ -523,6 +521,9 @@ abstract class kolab_api_service // add object type id to the result $attrs['type_id'] = $type_id; + // always return entrydn + $attrs['entrydn'] = $dn; + return $attrs; } diff --git a/lib/kolab_client_task.php b/lib/kolab_client_task.php index 35eec94..2c016e6 100644 --- a/lib/kolab_client_task.php +++ b/lib/kolab_client_task.php @@ -849,7 +849,7 @@ class kolab_client_task case 'list': $result['type'] = kolab_form::INPUT_TEXTAREA; - $result['data-type'] = kolab_form::TYPE_LIST; + $result['data-type'] = 'list'; if (!empty($field['maxlength'])) { $result['data-maxlength'] = $field['maxlength']; @@ -879,6 +879,24 @@ class kolab_client_task $result['default'] = $field['default']; break; + case 'aci': + $result['type'] = kolab_form::INPUT_TEXTAREA; + $result['data-type'] = 'aci'; + + $this->output->add_translation('aci.new', 'aci.edit', 'aci.remove', + 'aci.users', 'aci.rights', 'aci.targets', 'aci.aciname', + 'aci.read', 'aci.compare', 'aci.search', 'aci.write', 'aci.selfwrite', + 'aci.delete', 'aci.add', 'aci.proxy', 'aci.all', 'aci.allow', 'aci.deny', + 'aci.typeusers', 'aci.typegroups', 'aci.typeroles', 'aci.typeadmins', 'aci.typespecials', + 'aci.ldap-all', 'aci.ldap-anyone', 'aci.ldap-self', 'aci.ldap-parent', + 'aci.usersearch', 'aci.usersearchresult', 'aci.selected', 'aci.other', + 'aci.userselected', 'aci.useradd', 'aci.userremove', 'aci.thisentry', + 'aci.rights.target', 'aci.rights.filter', 'aci.rights.attrs', 'aci.checkall', 'aci.checknone', + 'aci.error.noname', 'aci.error.exists', 'aci.error.nousers', + 'button.cancel', 'button.ok' + ); + break; + default: $result['type'] = kolab_form::INPUT_TEXT; @@ -1260,7 +1278,7 @@ class kolab_client_task $value = $data[$idx]; // Convert data for the list field with autocompletion - if ($field['data-type'] == kolab_form::TYPE_LIST) { + if ($field['data-type'] == 'list') { if (!is_array($value)) { if (!empty($field['data-autocomplete'])) { $value = array($value => $value); @@ -1363,6 +1381,7 @@ class kolab_client_task $this->output->set_env('assoc_fields', $assoc_fields); $this->output->set_env('required_fields', $req_fields); $this->output->set_env('autocomplete_min_length', $ac_min_len); + $this->output->set_env('entrydn', $data['entrydn']); $this->output->add_translation('form.required.empty', 'form.maxcount.exceeded', $name . '.add.success', $name . '.edit.success', $name . '.delete.success', $name . '.delete.confirm', $name . '.delete.force', diff --git a/lib/kolab_form.php b/lib/kolab_form.php index 6ce7332..9ebf900 100644 --- a/lib/kolab_form.php +++ b/lib/kolab_form.php @@ -40,8 +40,6 @@ class kolab_form const INPUT_CONTENT = 20; const INPUT_TEXTQUOTA = 30; - const TYPE_LIST = 1; - private $attribs = array(); private $elements = array(); private $sections = array(); @@ -301,16 +299,6 @@ class kolab_form $attribs['cols'] = 50; } - if (!empty($attribs['data-type'])) { - switch ($attribs['data-type']) { - case self::TYPE_LIST: - $attribs['data-type'] = 'list'; - break; - default: - unset($attribs['data-type']); - } - } - $content = kolab_html::textarea($attribs, true); break; diff --git a/lib/locale/en_US.php b/lib/locale/en_US.php index 35dc51c..bc74504 100644 --- a/lib/locale/en_US.php +++ b/lib/locale/en_US.php @@ -7,6 +7,55 @@ $LANG['about.support'] = 'Professional support is available from <a href="http:/ $LANG['about.technology'] = 'Technology'; $LANG['about.warranty'] = 'It comes with absolutely <b>no warranties</b> and is typically run entirely self supported. You can find help & information on the community <a href="http://kolab.org">web site</a> & <a href="http://wiki.kolab.org">wiki</a>.'; +$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['add'] = 'Add'; $LANG['api.notypeid'] = 'No object type ID specified!'; @@ -36,6 +85,7 @@ $LANG['attribute.validate.extended'] = 'extended'; $LANG['button.cancel'] = 'Cancel'; $LANG['button.delete'] = 'Delete'; +$LANG['button.ok'] = 'OK'; $LANG['button.save'] = 'Save'; $LANG['button.submit'] = 'Submit'; @@ -136,6 +186,7 @@ $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'; diff --git a/public_html/js/kolab_admin.js b/public_html/js/kolab_admin.js index 171ae01..44c3227 100644 --- a/public_html/js/kolab_admin.js +++ b/public_html/js/kolab_admin.js @@ -2,7 +2,7 @@ +--------------------------------------------------------------------------+ | This file is part of the Kolab Web Admin Panel | | | - | Copyright (C) 2011-2012, Kolab Systems AG | + | Copyright (C) 2011-2014, Kolab Systems AG | | | | This program is free software: you can redistribute it and/or modify | | it under the terms of the GNU Affero General Public License as published | @@ -23,7 +23,7 @@ function kolab_admin() { - var ref = this; + var self = this; this.env = {}; this.translations = {}; @@ -34,8 +34,8 @@ function kolab_admin() // set jQuery ajax options $.ajaxSetup({ cache: false, - error: function(request, status, err) { ref.http_error(request, status, err); }, - beforeSend: function(xmlhttp) { xmlhttp.setRequestHeader('X-Session-Token', ref.env.token); } + error: function(request, status, err) { self.http_error(request, status, err); }, + beforeSend: function(xmlhttp) { xmlhttp.setRequestHeader('X-Session-Token', self.env.token); } }); /*********************************************************/ @@ -130,7 +130,7 @@ function kolab_admin() // set timer for requests if (a && this.env.request_timeout) - this.request_timer = window.setTimeout(function() { ref.request_timed_out(); }, this.request_timeout * 1000); + this.request_timer = window.setTimeout(function() { self.request_timed_out(); }, this.request_timeout * 1000); }; // called when a request timed out @@ -190,7 +190,7 @@ function kolab_admin() // display a system message (types: loading, notice, error) this.display_message = function(msg, type, timeout) { - var obj, ref = this; + var obj; if (!type) type = 'notice'; @@ -209,11 +209,11 @@ function kolab_admin() if (type != 'loading') { msg = '<div><span>' + msg + '</span></div>'; - obj.addClass(type).click(function() { return ref.hide_message(); }); + obj.addClass(type).click(function() { return self.hide_message(); }); } if (timeout > 0) - window.setTimeout(function() { ref.hide_message(type, type != 'loading'); }, timeout); + window.setTimeout(function() { self.hide_message(type, type != 'loading'); }, timeout); obj.attr('id', type == 'loading' ? 'loading' : 'message') .appendTo('body').html(msg).show(); @@ -234,6 +234,103 @@ function kolab_admin() $('#'+id).html(this.env.watermark); } + // modal dialog popup + this.modal_dialog = function(content, buttons, opts) + { + var settings = {btns: {}}, + body = $('<div class="modal"></div>'), + head, foot, footer = []; + + // title bar + if (opts && opts.title) + $('<div class="modal_header"></div>') + .append($('<span>').text(opts.title)) + .appendTo(body); + + // dialog content + if (typeof content != 'object') + content = $('<div></div>').html(content); + + content.addClass('modal_msg').appendTo(body); + + // buttons + $.each(buttons, function(i, v) { + var n = i.replace(/[^a-z0-9_]/ig, ''); + settings.btns[n] = v; + footer.push({name: n, label: self.t(i)}); + }); + +// if (!settings.btns.cancel && (!opts || !opts.no_cancel)) +// settings.btns.cancel = function() { this.hide(); }; + + if (footer.length) { + foot = $('<div class="modal_btns"></div>'); + $.each(footer, function() { + $('<div></div>').addClass('modal_btn_' + this.name).text(this.label).appendTo(foot); + }); + + body.append(foot); + } + + // configure and display dialog + body.wModal(settings).wModal('show'); + }; + + this.tree_list_init = function() + { + $('table.list.tree span.expando').click(function() { + var tr = $(this).parents('table.list.tree tr'), + expanded = tr.hasClass('expanded'), + level = tr.data('level') || 0, + row = tr[0], + found = false; + + tr[expanded ? 'removeClass' : 'addClass']('expanded'); + + $('tr', tr.parent()).each(function() { + if (this === row) { + found = true; + return; + } + + if (!found) + return; + + var r = $(this), l = r.data('level') || 0; + + if (l <= level) + return false; + + if (!expanded && l == level+1) + r.show(); + else if (expanded && l > level) + r.hide().removeClass('expanded'); + }); + + return false; + }); + }; + + // position and display popup + this.popup_show = function(e, popup) + { + var popup = $(popup), + pos = this.mouse_pos(e), + win = $(window), + w = popup.width(), + h = popup.height(), + left = pos.left - w, + top = pos.top; + + if (top + h > win.height()) + top -= h; + if (left + w > win.width()) + left -= w; + + popup.css({left: left + 'px', top: top + 'px'}).show(); + e.stopPropagation(); + }; + /********************************************************/ /********* Remote request methods *********/ @@ -369,93 +466,6 @@ function kolab_admin() }; - /********************************************************/ - /********* Helper methods *********/ - /********************************************************/ - - // disable/enable all fields of a form - this.lock_form = function(form, lock) - { - if (!form || !form.elements) - return; - - var n, len, elm; - - if (lock) - this.disabled_form_elements = []; - - for (n=0, len=form.elements.length; n<len; n++) { - elm = form.elements[n]; - - if (elm.type == 'hidden') - continue; - // remember which elem was disabled before lock - if (lock && elm.disabled) - this.disabled_form_elements.push(elm); - // check this.disabled_form_elements before inArray() as a workaround for FF5 bug - // http://bugs.jquery.com/ticket/9873 - else if (lock || (this.disabled_form_elements && $.inArray(elm, this.disabled_form_elements)<0)) - elm.disabled = lock; - } - }; - - this.set_request_time = function() - { - this.env.request_time = (new Date()).getTime(); - }; - - // Update request time element - this.update_request_time = function() - { - if (this.env.request_time) { - var t = ((new Date()).getTime() - this.env.request_time)/1000, - el = $('#reqtime'); - el.text(el.text().replace(/[0-9.,]+/, t)); - } - }; - - // position and display popup - this.popup_show = function(e, popup) - { - var popup = $(popup), - pos = this.mouse_pos(e), - win = $(window), - w = popup.width(), - h = popup.height(), - left = pos.left - w, - top = pos.top; - - if (top + h > win.height()) - top -= h; - if (left + w > win.width()) - left -= w; - - popup.css({left: left + 'px', top: top + 'px'}).show(); - e.stopPropagation(); - }; - - // Return absolute mouse position of an event - this.mouse_pos = function(e) - { - if (!e) e = window.event; - - var mX = (e.pageX) ? e.pageX : e.clientX, - mY = (e.pageY) ? e.pageY : e.clientY; - - if (document.body && document.all) { - mX += document.body.scrollLeft; - mY += document.body.scrollTop; - } - - if (e._offset) { - mX += e._offset.left; - mY += e._offset.top; - } - - return { left:mX, top:mY }; - }; - - /*********************************************************/ /********* keyboard autocomplete methods *********/ /*********************************************************/ @@ -708,8 +718,10 @@ function kolab_admin() // Form initialization this.form_init = function(id) { - var form = $('#'+id); + var form = $('#'+id), + aci_fields = $('textarea[data-type="aci"]', form); + this.aci = {}; this.trigger_event('form-load', id); // replace some textarea fields with pretty/smart input lists @@ -721,6 +733,10 @@ function kolab_admin() // create LDAP URL fields $('input[data-type="ldap_url"]:not(:disabled):not([readonly])', form) .each(function() { kadm.form_url_element_wrapper(this); }); + // create ACI fields + aci_fields.each(function() { kadm.form_aci_element_wrapper(this); }); + if (aci_fields.length) + this.form_aci_init(); }; // Form serialization @@ -761,6 +777,11 @@ function kolab_admin() data.json = kadm.form_url_element_submit(this.name, data.json, form); }); + // ACI fields + $('textarea[data-type="aci"]:not(:disabled):not([readonly])', form).each(function() { + data.json = kadm.form_aci_element_submit(this.name, data.json, form); + }); + // quota inputs $('input[data-type="quota"]', form).each(function() { var unit = $('select[name="' + this.name + '-unit"]').val(); @@ -1146,6 +1167,564 @@ function kolab_admin() return elem; }; + // initialize ACI fields in form + this.form_aci_init = function() + { + // get list of ldap attributes for ACI form + if (!this.ldap_attributes) { + this.api_post('form_value.select_options', {attributes: ['attribute']}, 'form_aci_init_response'); + } + }; + + this.form_aci_init_response = function(response) + { + if (!this.api_response(response)) + return; + + this.ldap_attributes = response.result.attribute ? response.result.attribute.list : []; + }; + + // Replaces form element with ACI element + this.form_aci_element_wrapper = function(form_element) + { + var i, e = $(form_element), + form = form_element.form, + name = form_element.name, + div = $('<div class="aci"></div>'), + select = $('<select multiple="multiple" size="8"></select>'), + table = $('<table class="acltable"><tr><td class="list"></td><td class="buttons"></td></tr></table>'), + buttons = [ + $('<input type="button" />').attr({value: this.t('aci.new')}), + $('<input type="button" />').attr({value: this.t('aci.edit'), disabled: true}), + $('<input type="button" />').attr({value: this.t('aci.remove'), disabled: true}) + ], + aci = this.parse_aci(e.val()) || []; + this.aci[name] = aci; + e.hide(); + + // this.log(e.val()); + // this.log(aci); + + $.each(aci, function(i, entry) { + $('<option></option>').val(i).text(entry.name).appendTo(select) + .on('dblclick', function () { self.form_aci_dialog(name, this.value); }); + }); + + select.attr('id', 'aci'+name).on('change', function() { + var selected = $(this).val() || []; + + buttons[1].prop('disabled', selected.length != 1); + buttons[2].prop('disabled', selected.length == 0); + }); + + // click on 'new' and 'edit' button + buttons[0].on('click', function() { self.form_aci_dialog(name); }); + buttons[1].on('click', function() { + var selected = select.val(); + self.form_aci_dialog(name, selected && selected.length ? selected[0] : null); + }); + + // click on 'remove' button + buttons[2].on('click', function() { + $.each(select.val() || [], function(i, v) { + self.aci[name][v] = null; + $('option[value="' + v + '"]', select).remove(); + }); + buttons[1].prop('disabled', true); + buttons[2].prop('disabled', true); + }); + + $('.buttons', table).append(buttons); + $('.list', table).append(select); + div.append(table) + + $(form_element).parent().append(div); + }; + + // updates form data with ACI (on form submit) + this.form_aci_element_submit = function(name, data, form) + { + data[name] = this.build_aci(this.aci[name]); + + return data; + }; + + // display aci dialog + this.form_aci_dialog = function(name, id) + { + var aci = id ? this.aci[name][id] : {}; + + this.aci_dialog_aci = aci; + this.aci_dialog_name = name; + this.aci_dialog_id = id; + + this.modal_dialog(this.form_aci_dialog_content(aci), this.form_aci_dialog_buttons()); + + window.setTimeout(function() { $('#aci-name').focus(); }, 100); + }; + + // return aci dialog buttons + this.form_aci_dialog_buttons = function() + { + var buttons = { + 'button.ok': function() { + if (self.form_aci_dialog_submit()) { + this.hide(); + $('#aci-dialog').remove(); + } + }, + 'button.cancel': function() { + this.hide(); + $('#aci-dialog').remove(); + }, + }; + + return buttons; + }; + + // build and return aci dialog content + this.form_aci_dialog_content = function(aci) + { + var dialog = $('#aci-dialog'); + + if (!dialog.length) { + var i, tabs = [ + $('<fieldset></fieldset>').attr('id', 'aci-tab-users') + .append($('<legend></legend>').text(this.t('aci.users'))) + .append(this.form_aci_dialog_tab_users()), + $('<fieldset></fieldset>').attr('id', 'aci-tab-rights') + .append($('<legend></legend>').text(this.t('aci.rights'))) + .append(this.form_aci_dialog_tab_rights()), + $('<fieldset></fieldset>').attr('id', 'aci-tab-targets') + .append($('<legend></legend>').text(this.t('aci.targets'))) + .append(this.form_aci_dialog_tab_targets()) + ]; + + dialog = $('<div id="aci-dialog"><label></label><input id="aci-name" type="text" size="40" />' + + '<form id="aci-form"></form></div>') + .hide().appendTo('body'); + + dialog.children('label').text(this.t('aci.aciname')); + + $('#aci-form').append(tabs); + + this.trigger_event('form-load', 'aci-form'); + } + + // reset form elements + this.form_aci_dialog_reset(aci); + + return dialog.show(); + }; + + this.form_aci_dialog_reset = function(aci) + { + var users = $('#aci-users').html(''), + rights = aci.perms ? aci.perms[0].rights : [], + inputs = $('#aci-rights input').prop('checked', false), + target = $('#aci-targets-target').val(''), + target_filter = $('#aci-targets-filter').val(''), + target_operator = $('#aci-targets input[name="attr-operator"]'), + rule = aci.perms ? aci.perms[0].type : 'userdn'; + + $.each(rights, function(i, v) { + $('#aci-rights-' + v).click(); + }); + + $('#aci-name').val(aci.name); + $('#aci-rights-type').val(aci.perms ? aci.perms[0].type : ''); + $('#aci-users-button-remove').prop('disabled', true); + target_operator.filter('[value="="]').prop('checked', true); + target_operator.filter('[value="!="]').prop('checked', false); + + $.each(aci.perms ? aci.perms : [], function(i, perm) { + $.each(perm.rules || [], function(n, rule) { + // these permission rules we do not support here + if (!/^(userdn|groupdn|roledn)$/i.test(rule.keyword) || rule.operator != '=') + return; + + $.each(rule.expression || [], function(x, v) { + if (v.substr(0, 8) == 'ldap:///') { + v = v.substr(8); + } + + var t = v; + if (t == 'all' || t == 'self' || t == 'anyone' || t == 'parent') + t = self.t('aci.ldap-' + t); + else if (/^cn=([^,]+)/.test(t)) + t = RegExp.$1; + // @TODO: resolve users DN with user names + + $('<option></option>').attr({value: rule.keyword + ':' + v}).text(t).appendTo(users); + }); + }); + }); + + $.each(aci.targets || [], function(i, v) { + switch (v.type) { + case 'targetfilter': + target_filter.val(v.expression); + break; + + case 'targetattr': + if (v.expression[0] == '*') + $('#aci-targets-attr option').prop('selected', true); + else + $('#aci-targets-attr').val(v.expression); + + target_operator.filter('[value="="]').prop('checked', v.operator == '='); + target_operator.filter('[value="!="]').prop('checked', v.operator == '!='); + break; + + case 'target': + target.val(v.expression); + break; + } + }); + }; + + // submits aci dialog, updates aci definition in form + this.form_aci_dialog_submit = function() + { + var val, rules = [], rights = [], + name_input = $('#aci-name'), + name = name_input.val(), + rights_type = $('#aci-rights-type').val(), + aci_list = $('#aci' + this.aci_dialog_name), + exists = false, + aci = {perms: [], targets: [], version: '3.0', name: name}; + + // sanity checks + if (!name) { + alert(this.t('aci.error.noname')); + name_input.focus(); + return false; + } + + $.each(this.aci[self.aci_dialog_name] || [], function(i, v) { + if (v && v.name == name && (!self.aci_dialog_id || self.aci_dialog_id != i)) { + exists = true; + return false; + } + }); + + if (exists) { + alert(this.t('aci.error.exists')); + name_input.focus(); + return false; + } + + // permissions + $('#aci-users option').each(function() { + var keyword, value = this.value; + + /^([a-z]+):/.test(value); + keyword = RegExp.$1; + value = value.substr(keyword.length + 1); + + rules.push({ + join: 'or', + operator: '=', + keyword: keyword, + expression: ['ldap:///' + value] + }); + }); + + if (!rules.length) { + alert(this.t('aci.error.nousers')); + return false; + } + + $('#aci-rights input').each(function() { + if (this.checked) { + if (this.value == 'all') { + rights = ['all']; + return false; + } + + rights.push(this.value); + } + }); + + if (!rights.length) { + rights = ['all']; + rights_type = rights_type == 'allow' ? 'deny' : 'allow'; + } + + aci.perms.push({ + rights: rights, + type: rights_type, + rules: rules + }); + + // targets + if ((v = $('#aci-targets-attr').val() || []).length) + aci.targets.push({ + type: 'targetattr', + expression: v.length == $('#aci-targets-attr option').length ? ['*'] : v, + operator: $('#aci-targets input[name="attr-operator"][value="!="]').is(':checked') ? '!=' : '=', + }); + + if (v = $('#aci-targets-target').val()) + aci.targets.push({ + type: 'target', + expression: v, + operator: '=' + }); + + if (v = $('#aci-targets-filter').val()) + aci.targets.push({ + type: 'targetfilter', + expression: v, + operator: '=' // @TODO, + }); + + // this.log(aci); + // this.log(this.build_aci([aci])); + + if (this.aci_dialog_id) { + this.aci[this.aci_dialog_name][this.aci_dialog_id] = aci; + $('option[value="' + this.aci_dialog_id + '"]', aci_list).text(aci.name); + } + else { + this.aci[this.aci_dialog_name].push(aci); + $('<option></option>').val(this.aci[this.aci_dialog_name].length-1) + .text(aci.name) + .appendTo(aci_list) + .on('dblclick', function () { self.form_aci_dialog(self.aci_dialog_name, this.value); }); + } + + return true; + }; + + // tab Users in aci dialog + this.form_aci_dialog_tab_users = function() + { + var select = $('<select id="aci-users" multiple="multiple" size="8"></select>'), + table = $('<table class="acltable"><tr><td class="list"></td><td class="buttons"></td></tr></table>'), + buttons = [ + $('<input type="button" />').attr({value: this.t('aci.new')}), + $('<input type="button" id="aci-users-button-remove" />').attr({value: this.t('aci.remove'), disabled: true}) + ]; + + select.on('change', function() { + var selected = $(this).val() || []; + buttons[1].attr('disabled', selected.length == 0); + }); + + // click on 'new' button + buttons[0].on('click', function() { self.form_aci_user_dialog(); }); + + // click on 'remove' button + buttons[1].on('click', function() { + $.each(select.val() || [], function(i, v) { + $('option[value="' + v + '"]', select).remove(); + }); + + $(this).prop('disabled', true); + }); + + $('.buttons', table).append(buttons); + $('.list', table).append(select); + + return table; + }; + + // tab Rights in aci dialog + this.form_aci_dialog_tab_rights = function() + { + var div = $('<div id="aci-rights"></div>'), + select = $('<select id="aci-rights-type"></select>'), + types = ['allow', 'deny'], + rights = ['read', 'compare', 'search', 'write', 'selfwrite', 'delete', 'add', 'proxy', 'all'], + inputs = []; + + $.each(rights, function(i, v) { + var input = $('<input type="checkbox" name="aci-right[]" />').attr({value: v, id: 'aci-rights-' + v}); + inputs.push($('<label for="aci-rights-' + v + '"></label>').text(self.t('aci.' + v)).prepend(input)); + + if (v == 'all') + input.on('change', function() { + var list = $('input:not(#aci-rights-all)', div); + + if (this.checked) + list.prop({checked: true, disabled: true}); + else + list.prop({disabled: false}); + }); + }); + + $.each(types, function(i, v) { + $('<option></option>').attr({value: v}).text(self.t('aci.' + v)).appendTo(select); + }); + + return div.append(select).append(inputs); + }; + + // tab Targets in aci dialog + this.form_aci_dialog_tab_targets = function() + { + var opts = [], + content = $('<div id="aci-targets"></div>'), + target = $('<input id="aci-targets-target" type="text" size="40" />'), + filter = $('<input id="aci-targets-filter" type="text" size="40" />'), + button = $('<input type="button" id="aci-targets-targetbtn" />').val(this.t('aci.thisentry')) + .on('click', function() { target.val(self.env.entrydn) }), + select = $('<select id="aci-targets-attr" multiple="multiple" size="8"></select>'), + radio = [ + $('<label>').text(this.t('aci.selected')).prepend($('<input type="radio" name="attr-operator" value="=" />')), + $('<label>').text(this.t('aci.other')).prepend($('<input type="radio" name="attr-operator" value="!=" />')) + ]; + + $.each(this.ldap_attributes, function(i, v) { + var o = document.createElement('option'); + o.value = v.toLowerCase(); + $(o).text(v); + opts.push(o); + }); + + if (opts.length) + select.append(opts); + + content.append([ + $('<label>').text(this.t('aci.rights.target')), $('<div>').append([target, button]), + $('<label>').text(this.t('aci.rights.filter')), $('<div>').append(filter), + $('<label>').text(this.t('aci.rights.attrs')), $('<div>').append([select, radio[0], radio[1]]) + ]); + + return content; + }; + + this.form_aci_user_dialog = function() + { + var dialog = $('#aci-dialog'), + content = $('<div id="aci-users-dialog"></div>'), + search = $('<input id="aci-users-search" type="text" />'), + search_btn = $('<input id="aci-users-search-button" type="button" />') + .val(this.t('aci.search')).on('click', function() { self.form_aci_user_search(); }), + results = $('<select id="aci-users-results" multiple="multiple" size="6"></select>'), + selected = $('<select id="aci-users-selected" multiple="multiple" size="6"></select>'), + groups = $('<select id="aci-users-group"></select>') + .on('change', function() { + results.html(''); + if (this.value == 'specials') + $.each(['self', 'all', 'anyone', 'parent'], function(i, v) { + if (!$('option[value="userdn:' + v + '"]', selected).length) + $('<option></option>').attr({value: 'userdn:' + v}).text(self.t('aci.ldap-' + v)) + .appendTo(results) + .on('dblclick', function() { self.form_aci_user_option_dblclick(this); }); + }); + }), + options = ['users', 'groups', 'roles', /* 'admins', */ 'specials'], + buttons = { + 'button.ok': function() { + self.form_aci_user_dialog_submit(); + this.hide(); + $('#aci-users-dialog').remove(); + // bring back the main dialog + self.modal_dialog(dialog, self.form_aci_dialog_buttons()); + }, + 'button.cancel': function() { + this.hide(); + $('#aci-users-dialog').remove(); + // bring back the main dialog + self.modal_dialog(dialog, self.form_aci_dialog_buttons()); + } + }; + + $.each(options, function(i, v) { + $('<option></option>').attr({value: v}).text(self.t('aci.type' + v)).appendTo(groups); + }); + + content.append([ + $('<label>').text(this.t('aci.usersearch')), $('<div>').append([search, groups, search_btn]), + $('<label>').text(this.t('aci.usersearchresult')), $('<div>').append(results), + $('<label>').text(this.t('aci.userselected')), $('<div>').append(selected) + ]); + + this.modal_dialog(content, buttons); + }; + + this.form_aci_user_dialog_submit = function() + { + var user_list = $('#aci-users'); + + $('#aci-users-selected option').each(function() { + if (!$('option[value="' + this.value + '"]', user_list).length) + $('<option></option>').attr({value: this.value}).text($(this).text()).appendTo(user_list); + }); + }; + + this.form_aci_user_search = function() + { + var search = $('#aci-users-search').val(), + type = $('#aci-users-group').val(), + val, props, attrs = { + users: ['displayname', 'cn'], + groups: ['cn'], + roles: ['cn'], + }; + + if (search == '') + return; + + if (type == 'specials') { + $('#aci-users-results option').each(function() { + $(this)[$(this).text().indexOf(search) == -1 ? 'hide' : 'show']; + }); + return; + } + + // reset results select + $('#aci-users-results').html(''); + + // build search post parameters + val = {type: 'both', value: search}; + props = {attributes: ['cn', 'mail'], page_size: 10, sort_by: 'cn', search: {}}; + $.each(attrs[type], function(i, v) { props.search[v] = val; }); + + // call search + this.set_busy(true, 'searching'); + this.api_post(type + '.list', props, 'form_aci_user_search_response'); + }; + + this.form_aci_user_search_response = function(response) + { + if (!this.api_response(response)) + return; + + var results = $('#aci-users-results'), + selected = $('#aci-users-selected'), + type = $('#aci-users-group').val(), + prefixes = {users: 'userdn:', groups: 'groupdn:', roles: 'roledn:'}, + prefix = prefixes[type] || prefixes.users; + + $.each(response.result.list || {}, function(i, v) { + var value = prefix + i; + if (!$('option[value="' + value + '"]', selected).length) { + name = v.cn; + + if (v.mail) + name += ' (' + v.mail + ')'; + + $('<option></option>').attr({value: value}).text(name) + .appendTo(results) + .on('dblclick', function() { self.form_aci_user_option_dblclick(this); }); + } + }); + }; + + this.form_aci_user_option_dblclick = function(elem) + { + var elem = $(elem), cloned = elem.clone(true), + target = $('#aci-users-' + (elem.parent().attr('id') == 'aci-users-results' ? 'selected' : 'results')); + + if (!$('option[value="' + elem.val() + '"]', target).length) { + cloned.appendTo(target); + elem.remove(); + } + }; + // Replaces form element with LDAP URL element this.form_url_element_wrapper = function(form_element) { @@ -1271,46 +1850,32 @@ function kolab_admin() /********* Forms *********/ /*********************************************************/ - this.tree_list_init = function() + // disable/enable all fields of a form + this.lock_form = function(form, lock) { - $('table.list.tree span.expando').click(function() { - var tr = $(this).parents('table.list.tree tr'), - expanded = tr.hasClass('expanded'), - level = tr.data('level') || 0, - row = tr[0], - found = false; - - tr[expanded ? 'removeClass' : 'addClass']('expanded'); - - $('tr', tr.parent()).each(function() { - if (this === row) { - found = true; - return; - } - - if (!found) - return; + if (!form || !form.elements) + return; - var r = $(this), l = r.data('level') || 0; + var n, len, elm; - if (l <= level) - return false; + if (lock) + this.disabled_form_elements = []; - if (!expanded && l == level+1) - r.show(); - else if (expanded && l > level) - r.hide().removeClass('expanded'); - }); + for (n=0, len=form.elements.length; n<len; n++) { + elm = form.elements[n]; - return false; - }); + if (elm.type == 'hidden') + continue; + // remember which elem was disabled before lock + if (lock && elm.disabled) + this.disabled_form_elements.push(elm); + // check this.disabled_form_elements before inArray() as a workaround for FF5 bug + // http://bugs.jquery.com/ticket/9873 + else if (lock || (this.disabled_form_elements && $.inArray(elm, this.disabled_form_elements)<0)) + elm.disabled = lock; + } }; - - /*********************************************************/ - /********* Forms *********/ - /*********************************************************/ - this.serialize_form = function(id) { var i, v, json = {}, @@ -2180,6 +2745,125 @@ function kolab_admin() $('input[name="' + f + '2"]').val(pass); }; + // convert ACI string into object + this.parse_aci = function(aci_str) + { + var aci = []; + + $.each((aci_str || '').split(/\r?\n/), function(i, str) { + + var s, target, permission, + or_rx = /\s*\|\|\s*/, + entry = {targets: [], perms: []}; + + // Syntax: (target)(version 3.0; acl "name";permission bindRules;) + + // target is optional and there can be many of it + while (/^\(target/.test(str)) { + if (s = str.match(/^\((target|targetattr|targetscope|targetcontrol|extop|targetfilter|targattrfilters)\s*([!=]+)\s*\"([^"]+)\"\)/)) { + target = {operator: s[2], type: s[1], expression: s[3]}; + + switch (target.type) { + case 'targetattr': + case 'targetcontrol': + case 'extop': + target.expression = target.expression.toLowerCase().split(or_rx); + break; + }; + + entry.targets.push(target); + str = str.substr(s[0].length); + } + } + + // there must be one version and acl entry + if (s = str.match(/^\s*\(version\s*([0-9.]+)\s*;\s*acl\s*\"([^"]+)\";\s*/)) { + entry.version = s[1]; + entry.name = s[2]; + + str = str.substr(s[0].length); + + // there can be multiple permission/bindRule blocks + $.each(str.split(';'), function(i, perm) { + if (permission = self.parse_aci_permission(perm)) + entry.perms.push(permission); + }); + } + + if (entry.name) + aci.push(entry); + }); + + return aci; + }; + + this.parse_aci_permission = function(perm) + { + var s, rule, permission; + + if (s = perm.match(/^\s*(allow|deny)\s*\(([a-zA-Z, ]+)\)(.*)/)) { + permission = {type: s[1], rules: [], rights: s[2].toLowerCase().split(/\s*,\s*/)}; + + rule = s[3].replace(/^[ (]+/, '').replace(/[ )]+$/, ''); + + while (s = rule.match(/^\s*(or|and|or not|and not)?\s*(userdn|groupdn|roledn|userattr|ip|dns|timeofday|dayofweek|authmethod|ssf)\s*([<>!=]+)\s*\"([^"]+)\"/i)) { + permission.rules.push({ + join: permission.rules.length ? (s[1] || '').toUpperCase() : null, + keyword: s[2], + operator: s[3], + expression: s[4].split(/\s*\|\|\s*/) + }); + + rule = rule.substr(s[0].length); + } + } + + return permission; + }; + + // convert ACI object into ACI array (of strings) + this.build_aci = function(aci) + { + var result = []; + + $.each(aci, function(i, entry) { + // skip removed entries + if (!entry) + return; + + var acl = [], tokens = []; + $.each(entry.targets || [], function(i, target) { + var txt = target.expression; + + if ($.isArray(txt)) + txt = txt.join(' || '); + + tokens.push('(' + target.type + ' ' + (target.operator || '=') + ' "' + txt + '")'); + }); + + acl.push('version ' + entry.version); + acl.push('acl "' + entry.name + '"'); + + $.each(entry.perms || [], function(i, perm) { + var num = 0, text = perm.type + ' (' + perm.rights.join(',') + ')'; + + $.each(perm.rules || [], function(n, rule) { + if (rule.join && num) + text += ' ' + rule.join; + text += ' ' + rule.keyword + ' ' + (rule.operator || '=') + ' "' + rule.expression.join(' || ') + '"'; + num++; + }); + + acl.push(text); + }); + + tokens.push('(' + acl.join('; ') + ';)'); + result.push(tokens.join('')); + }); + + return result; + }; + // LDAP URL parser this.parse_ldap_url = function(url) { @@ -2324,8 +3008,190 @@ function kolab_admin() return url; }; + this.set_request_time = function() + { + this.env.request_time = (new Date()).getTime(); + }; + + // Update request time element + this.update_request_time = function() + { + if (this.env.request_time) { + var t = ((new Date()).getTime() - this.env.request_time)/1000, + el = $('#reqtime'); + el.text(el.text().replace(/[0-9.,]+/, t)); + } + }; + + // Return absolute mouse position of an event + this.mouse_pos = function(e) + { + if (!e) e = window.event; + + var mX = (e.pageX) ? e.pageX : e.clientX, + mY = (e.pageY) ? e.pageY : e.clientY; + + if (document.body && document.all) { + mX += document.body.scrollLeft; + mY += document.body.scrollTop; + } + + if (e._offset) { + mX += e._offset.left; + mY += e._offset.top; + } + + return { left:mX, top:mY }; + }; + }; +/** + * Modal dialogs + */ +(function($) { + $.fn.wModal = function(option, settings) { + if (typeof option === 'object') { + settings = option; + } + else if (typeof option === 'string') { + var values = [], + elements = this.each(function() { + var data = $(this).data('modal'); + + if (data) { + if (option === 'show') + data.show(settings || {}); + else if (option === 'hide') + data.hide(settings || {}); + else if ($.fn.wModal.defaultSettings[option] !== undefined) { + if (settings !== undefined) + data.settings[option] = settings; + else + values.push(data.settings[option]); + } + } + }); + + if (values.length === 1) + return values[0]; + else if (values.length > 0) + return values; + else + return elements; + } + + return this.each(function() { + var _settings = $.extend({}, $.fn.wModal.defaultSettings, settings || {}), + modal = new Modal(_settings, $(this)), + $el = modal.generate(); + + modal.pixel.append($el); + + $(this).data('modal', modal); + }); + } + + $.fn.wModal.defaultSettings = {btns: {}, msg: null}; + + function Modal(settings, elem) + { + this.modal = null; + this.settings = settings; + this.elem = elem; + this.tempButtons = {}; + + return this; + } + + Modal.prototype = + { + generate: function() + { + var _this = this; + + if (this.modal) return this.modal; + + // bg - check if bg already exists + if ($('#modal_bg').length) { + this.bg = $('#modal_bg'); + this.pixel = $('#modal_pixel'); + } + else { + this.bg = $('<div id="modal_bg"></div>').css({position:'fixed', left:'0', top:'0', display:'none'}); + $('body').append(this.bg); + $(window).resize(function() { if(_this.bg.is(':visible')) _this.resetBg.apply(_this); }); + + // positioning pixel setting to body produces some weird effects with scrollbars when doing sliding effects + this.pixel = $('<div id="modal_pixel"></div>').css({position:'fixed', left:'0', top:'0', width:'0', height:'0', lineHeight:'0', fontSize:'0'}); + $('body').append(this.pixel); + } + + // modal + this.modal = $('<div class="modal_holder"></div>').css({position:'absolute', display:'none'}); + this.modal.append(this.elem); + + $(window).resize(function() { if(_this.modal.is(':visible')) _this.resetModal.apply(_this); }); + + this.resetBtns(); + + return this.modal; + }, + + resetModal: function() + { + var modalWidth = this.modal.outerWidth(true), + modalHeight = this.modal.outerHeight(true), + viewWidth = $(window).width(), + viewHeight = $(window).height(), + left = (viewWidth/2) - (modalWidth/2), + top = (viewHeight/2) - (modalHeight/2); + + this.modal.css({left:(left > 0 ? left + 'px' : 'auto'), top:(top > 0 ? top + 'px' : 'auto'), bottom: 'auto', right: 'auto'}); + }, + + resetBg: function() + { + this.bg.css({width:$(window).width(), height:$(window).height()}); + }, + + resetBtns: function(btns) + { + var btns = btns || this.settings.btns, + _this = this; + + for (var btn in btns) { + (function(btn) { + _this.modal.find('.modal_btn_' + btn).unbind('click'); + _this.modal.find('.modal_btn_' + btn).click(function() { + if (_this.tempBtns[btn]) + _this.tempBtns[btn].apply(_this); + else + btns[btn].apply(_this); + }); + })(btn); + } + }, + + show: function(settings) + { + this.tempBtns = settings.btns || {}; + this.resetBg(); + this.resetModal(); + this.pixel.children('.modal_holder').hide(); + + var _this = this; + this.bg.fadeIn(100, function(){ _this.modal.fadeIn(100); }); + }, + + hide: function() + { + this.modal.hide().remove(); + this.bg.hide(); + } + } +})(jQuery); + // Add escape() method to RegExp object // http://dev.rubyonrails.org/changeset/7271 RegExp.escape = function(str) diff --git a/public_html/skins/default/style.css b/public_html/skins/default/style.css index 7da1cc9..6b9ffef 100644 --- a/public_html/skins/default/style.css +++ b/public_html/skins/default/style.css @@ -772,7 +772,8 @@ span.form_error { padding-left: 5px; } -.ldap_url { +.ldap_url, +.aci { background-color: #F5F5F5; border: 1px solid #D0D0D0; border-radius: 3px 3px 3px 3px; @@ -825,6 +826,24 @@ table.form tr.required .ldap_url { height: 22px; } +.aci { + padding: 5px; +} + +.acltable select { + width: 400px; +} + +.acltable .buttons { + vertical-align: top; + text-align: center; +} + +.acltable .buttons input { + display: block; + width: 100px; +} + /***** autocomplete list *****/ #autocompletepane @@ -923,6 +942,156 @@ fieldset.tabbed border-top: none; } +/***** Dialog windows *****/ + +#modal_bg { + background-color: #000; + z-index: 10000; + opacity: 0.2; + filter: alpha(opacity=20); +} + +#modal_pixel { + z-index: 10001; +} + +.modal { + position: relative; + min-width: 350px; + overflow: hidden; + line-height: 15px; + background-color: #FFF; + color: #333; + border: 1px solid rgba(51, 51, 51, 0.5); + border-radius: 4px; + box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); +} + +.modal_header { + padding: 10px; + font-size: 14px; + font-weight: bold; + border-bottom: solid 1px #DDD; +} + +.modal_close { + position: absolute; + right: 10px; + top: 10px; + font-weight: normal; + font-size: 12px; + cursor: pointer; + color: #BABABA; +} + +.modal_msg { + font-size: 12px; + padding: 20px; + color: #3A3A3A; + text-shadow: rgba(255, 255, 255, 0.75) 0 1px 1px; +} + +.modal_btns { + padding: 10px; + font-size: 10px; + font-weight: bold; + border-top: solid 1px #DDD; + background-color: #EFEFEF; + text-align: right; + white-space: nowrap; +} + +.modal_btns div { + display: inline-block; + min-width: 40px; + padding: 0 10px; + height: 25px; + line-height: 25px; + margin-left: 10px; + text-align: center; + cursor: pointer; + border-radius: 4px; + box-shadow: rgba(255, 255, 255, 0.2) 0px 1px 0px 0px inset, rgba(0, 0, 0, 0.0470588) 0px 1px 2px 0px; + text-shadow: rgba(255, 255, 255, 0.75) 0 1px 1px; + border: 1px solid rgba(0, 0, 0, 0.14902); + border-bottom-color: rgba(0, 0, 0, 0.247059); + background-color: #F5F5F5; + color: #333; +} + +.modal_btns div:hover { + background-color: #E6E6E6; +} + +.modal_btns div.default { + text-shadow: rgba(0, 0, 0, 0.247059) 0px -1px 0px; + border: 1px solid rgba(0, 0, 0, 0.0980392); + background-color: #006DCC; + color: #FFF; +} + +.modal_btns div.default:hover { + background-color: #0044CC +} + +/**** ACI widget ********/ + +#aci-dialog, +#aci-users-dialog { + width: 550px; + background-color: #F0F0F0; + border: 1px solid #D0D0D0; + border-radius: 4px; + margin: 10px; + padding: 10px; +} + +#aci-users-dialog { + width: 400px; +} + +#aci-dialog fieldset { + background-color: #f0f0f0; +} + +#aci-dialog > label { + padding-right: 5px; +} + +#aci-name { + width: 420px; +} + +#aci-rights label, +#aci-users-results, +#aci-users-selected, +#aci-targets-attr { + display: block; +} + +#aci-rights-all { + padding-top: 5px; +} + +#aci-targets-attr, +#aci-users-results, +#aci-users-selected, +#aci-targets-target, +#aci-targets-filter { + width: 400px; +} + +#aci-users-dialog label, +#aci-targets label { + font-size: 11px; + font-color: #606060; +} + +#aci-users-dialog div, +#aci-targets div { + margin-bottom: 10px; +} + /**** Login form elements ****/ #login_form { diff --git a/public_html/skins/default/ui.js b/public_html/skins/default/ui.js index 11b513d..c0ed08c 100644 --- a/public_html/skins/default/ui.js +++ b/public_html/skins/default/ui.js @@ -97,7 +97,7 @@ function init_tabs(id, current) // create a tab a = $('<a>').text(legend.text()).attr('href', '#'); tab = $('<span>').attr({'id': 'tab'+idx, 'class': 'tablink'}) - .click(function() { show_tab(id, idx); return false; }) + .click(function() { show_tab(id, idx); return false; }) // remove legend legend.remove(); @@ -114,13 +114,13 @@ function init_tabs(id, current) function show_tab(id, index) { - var fs = $('#'+id).children('fieldset'); + var form = $('#'+id), fs = form.children('fieldset'); fs.each(function(idx) { // Show/hide fieldset (tab content) $(this)[index == idx ? 'show' : 'hide'](); // Select/unselect tab - $('#tab'+idx).toggleClass('tablink-selected', idx == index); + $('#tab'+idx, form).toggleClass('tablink-selected', idx == index); }); }; |