lib/api lib/Auth lib/client lib/ext lib/kolab_api_service.php lib/kolab_client_task.php lib/kolab_form.php lib/locale public_html/js public_html/skins
Aleksander Machniak
machniak at kolabsys.com
Fri Mar 21 16:07:07 CET 2014
lib/Auth/LDAP.php | 3
lib/api/kolab_api_service_form_value.php | 16
lib/api/kolab_api_service_ou.php | 4
lib/api/kolab_api_service_resource.php | 2
lib/api/kolab_api_service_sharedfolder.php | 2
lib/api/kolab_api_service_user.php | 8
lib/client/kolab_client_task_ou.php | 21
lib/client/kolab_client_task_settings.php | 4
lib/ext/Net/LDAP3.php | 2
lib/kolab_api_service.php | 71 -
lib/kolab_client_task.php | 23
lib/kolab_form.php | 12
lib/locale/en_US.php | 51 +
public_html/js/kolab_admin.js | 1240 ++++++++++++++++++++++++-----
public_html/skins/default/style.css | 171 +++
public_html/skins/default/ui.js | 6
16 files changed, 1374 insertions(+), 262 deletions(-)
New commits:
commit 5a62c7e3594c60b7f37c417d5a004010ffb8c767
Author: Aleksander Machniak <machniak at kolabsys.com>
Date: Fri Mar 21 16:05:35 2014 +0100
Add ACI form widget (Request #1782)
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();
@@ -1068,82 +1089,640 @@ function kolab_admin()
else if (e.which == 38 || e.which == 40) {
options = options.not(':hidden');
- if (options.length <= 1)
- return;
+ if (options.length <= 1)
+ return;
+
+ var focused,
+ selected = options.filter('.selected'),
+ index = options.index(selected);
+
+ if (e.which == 40) {
+ if (!(focused = options.get(index+1)))
+ focused = options.get(index-1);
+ }
+ else {
+ if (!(focused = options.get(index-1)))
+ focused = options.get(index+1);
+ }
+
+ if (focused) {
+ focused = $(focused);
+ selected.removeClass('selected');
+ focused.addClass('selected');
+
+ var parent = focused.parent(),
+ parent_height = parent.height(),
+ parent_top = parent.get(0).scrollTop,
+ top = focused.offset().top - parent.offset().top,
+ height = focused.height();
+
+ if (top < 0)
+ parent.get(0).scrollTop = 0;
+ else if (top >= parent_height)
+ parent.get(0).scrollTop = top - parent_height + height + parent_top;
+ }
+
+ return;
+ }
+
+ if (!s) {
+ options.show().removeClass('selected');
+ return;
+ }
+
+ options.each(function() {
+ var o = $(this), v = o.data('value');
+ o[v.indexOf(s) != -1 ? 'show' : 'hide']().removeClass('selected');
+ });
+
+ options = options.not(':hidden');
+ if (options.length == 1)
+ options.addClass('selected');
+ });
+
+ // add option rows
+ $.each(list, function(i, v) {
+ var elem = kadm.form_select_option_element(form, {value: v, key: v, element: e});
+ elem.appendTo(content);
+ });
+ };
+
+ // Creates option element for smart select
+ this.form_select_option_element = function(form, data)
+ {
+ // build element content
+ var elem = $('<span class="listelement"></span>')
+ .data('value', data.key).text(data.value)
+ .click(function(e) {
+ var val = $(this).data('value'),
+ elem = $(data.element),
+ old_val = elem.val();
+
+ $('span.link', elem.parent()).text(val);
+ elem.val(val);
+ if (val != old_val)
+ elem.change();
+ });
+
+ 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 = [];
- var focused,
- selected = options.filter('.selected'),
- index = options.index(selected);
+ $.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 (e.which == 40) {
- if (!(focused = options.get(index+1)))
- focused = options.get(index-1);
- }
- else {
- if (!(focused = options.get(index-1)))
- focused = options.get(index+1);
- }
+ if (v == 'all')
+ input.on('change', function() {
+ var list = $('input:not(#aci-rights-all)', div);
- if (focused) {
- focused = $(focused);
- selected.removeClass('selected');
- focused.addClass('selected');
+ if (this.checked)
+ list.prop({checked: true, disabled: true});
+ else
+ list.prop({disabled: false});
+ });
+ });
- var parent = focused.parent(),
- parent_height = parent.height(),
- parent_top = parent.get(0).scrollTop,
- top = focused.offset().top - parent.offset().top,
- height = focused.height();
+ $.each(types, function(i, v) {
+ $('<option></option>').attr({value: v}).text(self.t('aci.' + v)).appendTo(select);
+ });
- if (top < 0)
- parent.get(0).scrollTop = 0;
- else if (top >= parent_height)
- parent.get(0).scrollTop = top - parent_height + height + parent_top;
- }
+ 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);
+ });
- return;
+ 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());
}
+ };
- if (!s) {
- options.show().removeClass('selected');
- return;
- }
+ $.each(options, function(i, v) {
+ $('<option></option>').attr({value: v}).text(self.t('aci.type' + v)).appendTo(groups);
+ });
- options.each(function() {
- var o = $(this), v = o.data('value');
- o[v.indexOf(s) != -1 ? 'show' : 'hide']().removeClass('selected');
- });
+ 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)
+ ]);
- options = options.not(':hidden');
- if (options.length == 1)
- options.addClass('selected');
- });
+ this.modal_dialog(content, buttons);
+ };
- // add option rows
- $.each(list, function(i, v) {
- var elem = kadm.form_select_option_element(form, {value: v, key: v, element: e});
- elem.appendTo(content);
+ 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);
});
};
- // Creates option element for smart select
- this.form_select_option_element = function(form, data)
+ this.form_aci_user_search = function()
{
- // build element content
- var elem = $('<span class="listelement"></span>')
- .data('value', data.key).text(data.value)
- .click(function(e) {
- var val = $(this).data('value'),
- elem = $(data.element),
- old_val = elem.val();
+ var search = $('#aci-users-search').val(),
+ type = $('#aci-users-group').val(),
+ val, props, attrs = {
+ users: ['displayname', 'cn'],
+ groups: ['cn'],
+ roles: ['cn'],
+ };
- $('span.link', elem.parent()).text(val);
- elem.val(val);
- if (val != old_val)
- elem.change();
+ if (search == '')
+ return;
+
+ if (type == 'specials') {
+ $('#aci-users-results option').each(function() {
+ $(this)[$(this).text().indexOf(search) == -1 ? 'hide' : 'show'];
});
+ return;
+ }
- return elem;
+ // 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
@@ -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);
});
};
More information about the commits
mailing list