Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
118 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/kolab_client_task.php b/lib/kolab_client_task.php
index 07864ff..2aedb27 100644
--- a/lib/kolab_client_task.php
+++ b/lib/kolab_client_task.php
@@ -1,1596 +1,1600 @@
<?php
/*
+--------------------------------------------------------------------------+
| This file is part of the Kolab Web Admin Panel |
| |
| Copyright (C) 2011-2012, 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 |
| by the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/> |
+--------------------------------------------------------------------------+
| Author: Aleksander Machniak <machniak@kolabsys.com> |
| Author: Jeroen van Meeuwen <vanmeeuwen@kolabsys.com> |
+--------------------------------------------------------------------------+
*/
class kolab_client_task
{
/**
* @var kolab_client_output
*/
protected $output;
/**
* @var kolab_client_api
*/
protected $api;
/**
* @var Conf
*/
protected $config;
protected $ajax_only = false;
protected $page_title = 'Kolab Web Admin Panel';
protected $menu = array();
protected $cache = array();
protected $devel_mode = false;
protected $object_types = array('user', 'group', 'role', 'resource', 'sharedfolder', 'domain');
protected $page_size = 20;
protected $list_attribs = array();
protected $list_module;
protected $search_attribs = array(
'name' => array('cn', 'displayname'),
'email' => array('mail', 'alias', 'mailalternateaddress'),
'uid' => array('uid'),
);
protected static $translation = array();
/**
* Class constructor.
*
* @param kolab_client_output $output Optional output object
*/
public function __construct($output = null)
{
$this->config_init();
$this->devel_mode = $this->config_get('devel_mode', false, Conf::BOOL);
$this->output_init($output);
$this->api_init();
ini_set('session.use_cookies', 'On');
session_start();
$this->auth();
}
/**
* Localization initialization.
*/
protected function locale_init()
{
if (!empty(self::$translation)) {
return;
}
$language = $this->get_language();
$LANG = array();
if (!$language) {
$language = 'en_US';
}
@include INSTALL_PATH . '/locale/en_US.php';
if ($language != 'en_US') {
@include INSTALL_PATH . "/locale/$language.php";
}
setlocale(LC_ALL, $language . '.utf8', 'en_US.utf8');
self::$translation = $LANG;
}
/**
* Configuration initialization.
*/
private function config_init()
{
$this->config = Conf::get_instance();
}
/**
* Output initialization.
*/
private function output_init($output = null)
{
if ($output) {
$this->output = $output;
return;
}
$skin = $this->config_get('skin', 'default');
$this->output = new kolab_client_output($skin);
// Assign self to template variable
$this->output->assign('engine', $this);
}
/**
* API initialization
*/
private function api_init()
{
$url = $this->config_get('api_url', '');
if (!$url) {
$port = $_SERVER['SERVER_PORT'];
$url = kolab_utils::https_check() ? 'https://' : 'http://';
$url .= $_SERVER['SERVER_NAME'] . ($port ? ":$port" : '');
$url .= preg_replace('/\/?\?.*$/', '', $_SERVER['REQUEST_URI']);
$url .= '/api';
}
// TODO: Debug logging
//console($url);
$this->api = new kolab_client_api($url);
}
/**
* Returns system language (locale) setting.
*
* @return string Language code
*/
private function get_language()
{
$aliases = array(
'de' => 'de_DE',
'en' => 'en_US',
'pl' => 'pl_PL',
);
// UI language
$langs = !empty($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : '';
$langs = explode(',', $langs);
if (!empty($_SESSION['user']) && !empty($_SESSION['user']['language'])) {
array_unshift($langs, $_SESSION['user']['language']);
}
while ($lang = array_shift($langs)) {
$lang = explode(';', $lang);
$lang = $lang[0];
$lang = str_replace('-', '_', $lang);
if (file_exists(INSTALL_PATH . "/locale/$lang.php")) {
return $lang;
}
if (isset($aliases[$lang]) && ($alias = $aliases[$lang])
&& file_exists(INSTALL_PATH . "/locale/$alias.php")
) {
return $alias;
}
}
return null;
}
/**
* User authentication (and authorization).
*/
private function auth()
{
if (isset($_POST['login'])) {
$login = $this->get_input('login', 'POST');
if ($login['username']) {
$result = $this->api->login($login['username'], $login['password'], $login['domain']);
if ($token = $result->get('session_token')) {
$user = array(
'token' => $token,
'id' => $result->get('userid'),
'domain' => $result->get('domain')
);
$this->api->set_session_token($user['token']);
// Find user settings
// Don't call API user.info for non-existing users (#1025)
if (preg_match('/^cn=([a-z ]+)/i', $login['username'], $m)) {
$user['fullname'] = ucwords($m[1]);
}
else {
$res = $this->api->get('user.info', array('id' => $user['id']));
$res = $res->get();
if (is_array($res) && !empty($res)) {
$user['language'] = $res['preferredlanguage'];
$user['fullname'] = $res['cn'];
// overwrite user id set in login request, which is user base DN,
// with unique attribute, which suits better to our needs
$user['id'] = $res['id'];
}
}
// Save user data
$_SESSION['user'] = $user;
if (($language = $this->get_language()) && $language != 'en_US') {
$_SESSION['user']['language'] = $language;
$session_config['language'] = $language;
}
// Configure API session
if (!empty($session_config)) {
$this->api->post('system.configure', null, $session_config);
}
header('Location: ?');
die;
}
else {
$code = $result->get_error_code();
$str = $result->get_error_str();
$label = 'loginerror';
if ($code == kolab_client_api::ERROR_INTERNAL
|| $code == kolab_client_api::ERROR_CONNECTION
) {
$label = 'internalerror';
$this->raise_error(500, 'Login failed. ' . $str);
}
$this->output->command('display_message', $label, 'error');
}
}
}
else if (!empty($_SESSION['user']) && !empty($_SESSION['user']['token'])) {
// Validate session
$timeout = $this->config_get('session_timeout', 3600);
if ($timeout && $_SESSION['time'] && $_SESSION['time'] < time() - $timeout) {
$this->action_logout(true);
}
// update session time
$_SESSION['time'] = time();
// Set API session key
$this->api->set_session_token($_SESSION['user']['token']);
}
}
/**
* Main execution.
*/
public function run()
{
// Initialize locales
$this->locale_init();
// Session check
if (empty($_SESSION['user']) || empty($_SESSION['user']['token'])) {
$this->action_logout();
}
// Run security checks
$this->input_checks();
$this->action = $this->get_input('action', 'GET');
if ($this->action) {
$method = 'action_' . $this->action;
if (method_exists($this, $method)) {
$this->$method();
}
}
else if (method_exists($this, 'action_default')) {
$this->action_default();
}
}
/**
* Security checks and input validation.
*/
public function input_checks()
{
$ajax = $this->output->is_ajax();
// Check AJAX-only tasks
if ($this->ajax_only && !$ajax) {
$this->raise_error(500, 'Invalid request type!', null, true);
}
// CSRF prevention
$token = $ajax ? kolab_utils::get_request_header('X-Session-Token') : $this->get_input('token');
$task = $this->get_task();
if ($task != 'main' && $token != $_SESSION['user']['token']) {
$this->raise_error(403, 'Invalid request data!', null, true);
}
}
/**
* Logout action.
*/
private function action_logout($sess_expired = false, $stop_sess = true)
{
// Initialize locales
$this->locale_init();
if (!empty($_SESSION['user']) && !empty($_SESSION['user']['token']) && $stop_sess) {
$this->api->logout();
}
$_SESSION = array();
if ($this->output->is_ajax()) {
if ($sess_expired) {
$args = array('error' => 'session.expired');
}
$this->output->command('main_logout', $args);
if ($sess_expired) {
$this->output->send();
exit;
}
}
else {
$this->output->add_translation('loginerror', 'internalerror', 'session.expired');
}
if ($sess_expired) {
$error = 'session.expired';
}
else {
$error = $this->get_input('error', 'GET');
}
if ($error) {
$this->output->command('display_message', $error, 'error', 60000);
}
$this->send('login');
exit;
}
/**
* Error action (with error logging).
*
* @param int $code Error code
* @param string $msg Error message
* @param array $args Optional arguments (type, file, line)
* @param bool $output Enable to send output and finish
*/
public function raise_error($code, $msg, $args = array(), $output = false)
{
$log_line = sprintf("%s Error: %s (%s)",
isset($args['type']) ? $args['type'] : 'PHP',
$msg . (isset($args['file']) ? sprintf(' in %s on line %d', $args['file'], $args['line']) : ''),
$_SERVER['REQUEST_METHOD']);
write_log('errors', $log_line);
if (!$output) {
return;
}
if ($this->output->is_ajax()) {
header("HTTP/1.0 $code $msg");
die;
}
$this->output->assign('error_code', $code);
$this->output->assign('error_message', $msg);
$this->send('error');
exit;
}
/**
* Output sending.
*/
public function send($template = null)
{
if (!$template) {
$template = $this->get_task();
}
if ($this->page_title) {
$this->output->assign('pagetitle', $this->page_title);
}
$this->output->send($template);
exit;
}
/**
* Returns name of the current task.
*
* @return string Task name
*/
public function get_task()
{
$class_name = get_class($this);
if (preg_match('/^kolab_client_task_([a-z]+)$/', $class_name, $m)) {
return $m[1];
}
}
/**
* Returns output environment variable value
*
* @param string $name Variable name
*
* @return mixed Variable value
*/
public function get_env($name)
{
return $this->output->get_env($name);
}
/**
* Returns configuration option value.
*
* @param string $name Option name
* @param mixed $fallback Default value
* @param int $type Value type (one of Conf class constants)
*
* @return mixed Option value
*/
public function config_get($name, $fallback = null, $type = null)
{
$value = $this->config->get('kolab_wap', $name, $type);
return $value !== null ? $value : $fallback;
}
/**
* Returns translation of defined label/message.
*
* @return string Translated string.
*/
public static function translate()
{
$args = func_get_args();
if (is_array($args[0])) {
$args = $args[0];
}
$label = $args[0];
if (isset(self::$translation[$label])) {
$content = trim(self::$translation[$label]);
}
else {
$content = $label;
}
for ($i = 1, $len = count($args); $i < $len; $i++) {
$content = str_replace('$'.$i, $args[$i], $content);
}
return $content;
}
/**
* Returns input parameter value.
*
* @param string $name Parameter name
* @param string $type Parameter type (GET|POST|NULL)
* @param bool $allow_html Disables stripping of insecure content (HTML tags)
*
* @see kolab_utils::get_input
* @return mixed Input value.
*/
public static function get_input($name, $type = null, $allow_html = false)
{
if ($type == 'GET') {
$type = kolab_utils::REQUEST_GET;
}
else if ($type == 'POST') {
$type = kolab_utils::REQUEST_POST;
}
else {
$type = kolab_utils::REQUEST_ANY;
}
$result = kolab_utils::get_input($name, $type, $allow_html);
return $result;
}
/**
* Returns task menu output.
*
* @return string HTML output
*/
protected function menu()
{
if (empty($this->menu)) {
return '';
}
$menu = array();
$task = $this->get_task();
$caps = (array) $this->get_capability('actions');
foreach ($this->menu as $idx => $label) {
if (in_array($task, array('domain', 'group', 'resource', 'role', 'user'))) {
if (!array_key_exists($task . "." . $idx, $caps)) {
continue;
}
}
if (strpos($idx, '.')) {
$action = $idx;
$class = preg_replace('/\.[a-z_-]+$/', '', $idx);
}
else {
$action = $task . '.' . $idx;
$class = $idx;
}
$menu[$idx] = sprintf('<li class="%s">'
.'<a href="#%s" onclick="return kadm.command(\'%s\', \'\', this)">%s</a></li>',
$class, $idx, $action, $this->translate($label));
}
return '<ul>' . implode("\n", $menu) . '</ul>';
}
/**
* Adds watermark page definition into main page.
*/
protected function watermark($name)
{
$this->output->command('set_watermark', $name);
}
/**
* API GET request wrapper
*/
protected function api_get($action, $get = array())
{
return $this->api_call('get', $action, $get);
}
/**
* API POST request wrapper
*/
protected function api_post($action, $get = array(), $post = array())
{
return $this->api_call('post', $action, $get, $post);
}
/**
* API request wrapper with error handling
*/
protected function api_call($type, $action, $get = array(), $post = array())
{
if ($type == 'post') {
$result = $this->api->post($action, $get, $post);
}
else {
$result = $this->api->get($action, $get);
}
// error handling
if ($code = $result->get_error_code()) {
// Invalid session, do logout
if ($code == 403) {
$this->action_logout(true, false);
}
// Log communication errors, other should be logged on API side
if ($code < 400) {
$this->raise_error($code, 'API Error: ' . $result->get_error_str());
}
}
return $result;
}
/**
* Returns list of object types.
*
* @para string $type Object type name
* @param string $used_for Used_for attribute of object type
*
* @return array List of user types
*/
protected function object_types($type, $used_for = null)
{
if (empty($type) || !in_array($type, $this->object_types)) {
return array();
}
$cache_idx = $type . '_types' . ($used_for ? ":$used_for" : '');
if (!array_key_exists($cache_idx, $this->cache)) {
$result = $this->api_post($type . '_types.list');
$list = $result->get('list');
if (!empty($used_for) && is_array($list)) {
foreach ($list as $type_id => $type_attrs) {
if ($type_attrs['used_for'] != $used_for) {
unset($list[$type_id]);
}
}
}
$this->cache[$cache_idx] = !empty($list) ? $list : array();
Log::trace("kolab_client_task::${type}_types() returns: " . var_export($list, true));
}
return $this->cache[$cache_idx];
}
/**
* Returns user name.
*
* @param string $dn User DN attribute value
*
* @return string User name (displayname)
*/
protected function user_name($dn)
{
if (!$this->devel_mode) {
if (!empty($this->cache['user_names']) && isset($this->cache['user_names'][$dn])) {
return $this->cache['user_names'][$dn];
}
}
$result = $this->api_get('user.info', array('id' => $dn));
$username = $result->get('displayname');
if (empty($username)) {
$username = $result->get('cn');
}
if (empty($username)) {
if (preg_match('/^cn=([a-zA=Z ]+)/', $dn, $m)) {
$username = ucwords($m[1]);
}
}
if (!$this->devel_mode) {
$this->cache['user_names'][$dn] = $username;
}
return $username;
}
/**
* Returns list of system capabilities.
*
* @param bool $all If enabled capabilities for all domains will be returned
* @param bool $refresh Disable session cache
*
* @return array List of system capabilities
*/
protected function capabilities($all = false, $refresh = false)
{
if (!$refresh && isset($_SESSION['capabilities']) && !$this->devel_mode) {
$list = $_SESSION['capabilities'];
}
else {
$result = $this->api_post('system.capabilities');
$list = $result->get('list');
if (is_array($list) && !$this->devel_mode) {
$_SESSION['capabilities'] = $list;
}
}
$domain = $this->domain ? $this->domain : $_SESSION['user']['domain'];
return !$all ? $list[$domain] : $list;
}
/**
* Returns system capability
*
* @param string $name Capability (key) name
*
* @return array Capability value if supported, NULL otherwise
*/
protected function get_capability($name)
{
$caps = $this->capabilities();
return $caps[$name];
}
/**
* Returns domains list (based on capabilities response)
*
* @param bool $refresh Refresh session cache
*
* @return array List of domains
*/
protected function get_domains($refresh = false)
{
$caps = $this->capabilities(true, $refresh);
return is_array($caps) ? array_keys($caps) : array();
}
/**
* Returns effective rights for the specified object
*
* @param string $type Object type
* @param string $id Object identifier
*
* @return array Two element array with 'attribute' and 'entry' elements
*/
protected function effective_rights($type, $id = null)
{
$caps = $this->get_capability('actions');
if (empty($caps[$type . '.effective_rights'])) {
return array(
'attribute' => array(),
'entry' => array(),
);
}
// Get the rights on the entry and attribute level
$result = $this->api_get($type . '.effective_rights', array('id' => $id));
$result = array(
'attribute' => $result->get('attributeLevelRights'),
'entry' => $result->get('entryLevelRights'),
);
return $result;
}
/**
* Returns execution time in seconds
*
* @param string Execution time
*/
public function gentime()
{
return sprintf('%.4f', microtime(true) - KADM_START);
}
/**
* Returns HTML output of login form
*
* @param string HTML output
*/
public function login_form()
{
$post = $this->get_input('login', 'POST');
$username = kolab_html::label(array(
'for' => 'login_name',
'content' => $this->translate('login.username')), true)
. kolab_html::input(array(
'type' => 'text',
'id' => 'login_name',
'name' => 'login[username]',
'value' => $post['username'],
'autofocus' => true));
$password = kolab_html::label(array(
'for' => 'login_pass',
'content' => $this->translate('login.password')), true)
. kolab_html::input(array(
'type' => 'password',
'id' => 'login_pass',
'name' => 'login[password]',
'value' => ''));
$button = kolab_html::input(array(
'type' => 'submit',
'id' => 'login_submit',
'value' => $this->translate('login.login')));
$form = kolab_html::form(array(
'id' => 'login_form',
'name' => 'login',
'method' => 'post',
'action' => '?'),
kolab_html::span(array('content' => $username))
. kolab_html::span(array('content' => $password))
. $button);
return $form;
}
/**
* Returns form element definition based on field attributes
*
* @param array $field Field attributes
* @param array $data Attribute values
*
* @return array Field definition
*/
protected function form_element_type($field, $data = array())
{
$result = array();
switch ($field['type']) {
case 'select':
case 'multiselect':
$opts = $this->form_element_select_data($field, $data);
$result['type'] = kolab_form::INPUT_SELECT;
$result['options'] = $opts['options'];
$result['value'] = $opts['default'];
$result['default'] = $field['default'];
if ($field['type'] == 'multiselect') {
$result['multiple'] = true;
}
break;
case 'list':
$result['type'] = kolab_form::INPUT_TEXTAREA;
$result['data-type'] = kolab_form::TYPE_LIST;
if (!empty($field['maxlength'])) {
$result['data-maxlength'] = $field['maxlength'];
}
if (!empty($field['maxcount'])) {
$result['data-maxcount'] = $field['maxcount'];
}
if (!empty($field['autocomplete'])) {
$result['data-autocomplete'] = true;
}
break;
case 'checkbox':
$result['type'] = kolab_form::INPUT_CHECKBOX;
break;
case 'password':
$result['type'] = kolab_form::INPUT_PASSWORD;
if (isset($field['maxlength'])) {
$result['maxlength'] = $field['maxlength'];
}
break;
case 'text-quota':
$result['type'] = kolab_form::INPUT_TEXTQUOTA;
$result['default'] = $field['default'];
break;
default:
$result['type'] = kolab_form::INPUT_TEXT;
if (isset($field['maxlength'])) {
$result['maxlength'] = $field['maxlength'];
}
if ($field['type'] && $field['type'] != 'text') {
$result['data-type'] = $field['type'];
if ($field['type'] == 'ldap_url') {
$this->output->add_translation('ldap.one', 'ldap.sub', 'ldap.base',
'ldap.host', 'ldap.basedn','ldap.scope', 'ldap.conditions',
'ldap.filter_any', 'ldap.filter_both', 'ldap.filter_prefix', 'ldap.filter_suffix',
'ldap.filter_exact'
);
}
}
else {
$result['default'] = $field['default'];
}
}
$result['required'] = empty($field['optional']);
return $result;
}
/**
* Prepares options/value of select element
*
* @param array $field Field attributes
* @param array $data Attribute values
* @param bool $lc Convert option values to lower-case
*
* @return array Options/Default definition
*/
protected function form_element_select_data($field, $data = array(), $lc = false)
{
$options = array();
$default = null;
if (!isset($field['values'])) {
$data['attributes'] = array($field['name']);
$resp = $this->api_post('form_value.select_options', null, $data);
$resp = $resp->get($field['name']);
unset($data['attributes']);
$default = $resp['default'];
$field['values'] = $resp['list'];
}
if (!empty($field['values'])) {
if ($lc) {
$options = array_combine(array_map('strtolower', $field['values']), $field['values']);
}
else {
$options = array_combine($field['values'], $field['values']);
}
// Exceptions
if ($field['name'] == 'ou') {
foreach ($options as $idx => $dn) {
$options[$idx] = kolab_utils::dn2ufn($dn);
}
}
}
return array(
'options' => $options,
'default' => $default,
);
}
/**
* HTML Form elements preparation.
*
* @param string $name Object name (user, group, etc.)
* @param array $data Object data
* @param array $extra_fields Extra field names
*
* @return array Fields list, Object types list, Current type ID
*/
protected function form_prepare($name, &$data, $extra_fields = array(), $used_for = null)
{
$types = (array) $this->object_types($name, $used_for);
$add_mode = empty($data['id']);
$event_fields = array();
$auto_fields = array();
$form_fields = array();
$fields = array();
$auto_attribs = array();
$extra_fields = array_flip($extra_fields);
// Object type
$data['object_type'] = $name;
// Selected account type
if (!empty($data['type_id'])) {
$type = $data['type_id'];
}
else {
$data['type_id'] = $type = key($types);
}
if ($type) {
$auto_fields = (array) $types[$type]['attributes']['auto_form_fields'];
$form_fields = (array) $types[$type]['attributes']['form_fields'];
}
// Mark automatically generated fields as read-only, etc.
foreach ($auto_fields as $idx => $field) {
//console("\$field value for \$auto_fields[\$idx] (idx: $idx)", $auto_fields[$idx]);
if (!is_array($field)) {
//console("not an array... unsetting");
unset($auto_fields[$idx]);
continue;
}
// merge with field definition from
if (isset($form_fields[$idx])) {
$field = array_merge($field, $form_fields[$idx]);
}
// remove auto-generated value on type change, it will be re-generated
else if ($add_mode) {
unset($data[$idx]);
}
$field['name'] = $idx;
$fields[$idx] = $this->form_element_type($field, $data);
$fields[$idx]['readonly'] = true;
$extra_fields[$idx] = true;
// build auto_attribs and event_fields lists
$is_data = 0;
if (!empty($field['data'])) {
foreach ($field['data'] as $fd) {
$event_fields[$fd][] = $idx;
if (isset($data[$fd])) {
$is_data++;
}
}
if (count($field['data']) == $is_data) {
$auto_attribs[] = $idx;
}
}
else {
//console("\$field['data'] is empty for \$auto_fields[\$idx] (idx: $idx)");
$auto_attribs[] = $idx;
// Unset the $auto_field array key to prevent the form field from
// becoming disabled/readonly
unset($auto_fields[$idx]);
}
}
// Other fields
foreach ($form_fields as $idx => $field) {
if (!isset($fields[$idx])) {
$field['name'] = $idx;
$fields[$idx] = $this->form_element_type($field, $data);
}
else {
unset($extra_fields[$idx]);
}
$fields[$idx]['readonly'] = false;
// Attach on-change events to some fields, to update
// auto-generated field values
if (!empty($event_fields[$idx])) {
$event = json_encode(array_unique($event_fields[$idx]));
$fields[$idx]['onchange'] = "kadm.form_value_change($event)";
}
}
// Get the rights on the entry and attribute level
$data['effective_rights'] = $this->effective_rights($name, $data['id']);
$attribute_rights = (array) $data['effective_rights']['attribute'];
$entry_rights = (array) $data['effective_rights']['entry'];
// See if "administrators" (those who can delete and add back on the entry
// level) may override the automatically generated contents of auto_form_fields.
$admin_auto_fields_rw = $this->config_get('admin_auto_fields_rw', false, Conf::BOOL);
foreach ($fields as $idx => $field) {
if (!array_key_exists($idx, $attribute_rights)) {
// If the entry level rights contain 'add' and 'delete', well, you're an admin
if (in_array('add', $entry_rights) && in_array('delete', $entry_rights)) {
if ($admin_auto_fields_rw) {
$fields[$idx]['readonly'] = false;
}
}
else {
$fields[$idx]['readonly'] = true;
}
}
else {
if (in_array('add', $entry_rights) && in_array('delete', $entry_rights)) {
if ($admin_auto_fields_rw) {
$fields[$idx]['readonly'] = false;
}
}
// Explicit attribute level rights, check for 'write'
elseif (!in_array('write', $attribute_rights[$idx])) {
$fields[$idx]['readonly'] = true;
}
}
}
// Register list of auto-generated fields
$this->output->set_env('auto_fields', $auto_fields);
// Register list of disabled fields
$this->output->set_env('extra_fields', array_keys($extra_fields));
// (Re-|Pre-)populate auto_form_fields
if ($add_mode) {
if (!empty($auto_attribs)) {
$data['attributes'] = $auto_attribs;
$resp = $this->api_post('form_value.generate', null, $data);
$data = array_merge((array)$data, (array)$resp->get());
unset($data['attributes']);
}
}
else {
// Add common information fields
$add_fields = array(
'creatorsname' => 'createtimestamp',
'modifiersname' => 'modifytimestamp',
);
foreach ($add_fields as $idx => $val) {
if (!empty($data[$idx])) {
if ($value = $this->user_name($data[$idx])) {
if ($data[$val]) {
$value .= ' (' . strftime('%x %X', strtotime($data[$val])) . ')';
}
$fields[$idx] = array(
'label' => $idx,
'section' => 'system',
'value' => $value,
);
}
}
}
// Add debug information
if ($this->devel_mode) {
ksort($data);
$debug = kolab_html::escape(print_r($data, true));
$debug = preg_replace('/(^Array\n\(|\n*\)$|\t)/', '', $debug);
$debug = str_replace("\n ", "\n", $debug);
$debug = '<pre class="debug">' . $debug . '</pre>';
$fields['debug'] = array(
'label' => 'debug',
'section' => 'system',
'value' => $debug,
);
}
}
// Add object type hidden field
$fields['object_type'] = array(
'section' => 'system',
'type' => kolab_form::INPUT_HIDDEN,
'value' => $name,
);
// Get user-friendly names for lists
foreach ($fields as $fname => $field) {
if (!empty($field['data-autocomplete']) && !empty($data[$fname])) {
if (!is_array($data[$fname])) {
$data[$fname] = (array) $data[$fname];
}
//console("The data for field $fname at this point is", $data[$fname]);
// request parameters
$post = array(
'list' => $data[$fname],
'attribute' => $fname,
'object_type' => $name,
'type_id' => $data['type_id'],
);
// get options list
$result = $this->api_post('form_value.list_options', null, $post);
$result = $result->get('list');
$data[$fname] = $result;
//console("Set \$data['$fname'] to", $result);
}
}
// Add entry identifier
if (!$add_mode) {
$fields['id'] = array(
'section' => 'system',
'type' => kolab_form::INPUT_HIDDEN,
'value' => $data['id']
);
}
$result = array($fields, $types, $type);
return $result;
}
/**
* HTML Form creation.
*
* @param string $name Object name (user, group, etc.)
* @param array $attribs HTML attributes of the form
* @param array $sections List of form sections
* @param array $fields Fields list (from self::form_prepare())
* @param array $fields_map Fields map (used for sorting and sections assignment)
* @param array $data Object data (with effective rights, see form_prepare())
*
* @return kolab_form HTML Form object
*/
protected function form_create($name, $attribs, $sections, $fields, $fields_map, $data, $add_mode)
{
//console("Creating form for $name with data", $data);
//console("Assign fields to sections", $fields);
// Assign sections to fields
foreach ($fields as $idx => $field) {
if (!$field['section']) {
$fields[$idx]['section'] = isset($fields_map[$idx]) ? $fields_map[$idx] : 'other';
//console("Assigned field $idx to section " . $fields[$idx]['section']);
}
}
//console("Using fields_map", $fields_map);
// Sort
foreach ($fields_map as $idx => $val) {
if (array_key_exists($idx, $fields)) {
$fields_map[$idx] = $fields[$idx];
unset($fields[$idx]);
}
else {
unset($fields_map[$idx]);
}
}
if (!empty($fields)) {
$fields_map = array_merge($fields_map, $fields);
}
//console("Using attribs", $attribs);
$form = new kolab_form($attribs);
$assoc_fields = array();
$req_fields = array();
$writeable = 0;
$auto_fields = $this->output->get_env('auto_fields');
//console("form_create() \$attribs", $attribs);
//console("form_create() \$auto_fields", $auto_fields);
//console("Going to walk through sections", $sections);
// Parse elements and add them to the form object
foreach ($sections as $section_idx => $section) {
$form->add_section($section_idx, kolab_html::escape($this->translate($section)));
foreach ($fields_map as $idx => $field) {
if ($field['section'] != $section_idx) {
continue;
}
if (empty($field['label'])) {
$field['label'] = "$name.$idx";
}
$field['label'] = kolab_html::escape($this->translate($field['label']));
$field['description'] = "$name.$idx.desc";
$field['section'] = $section_idx;
if (empty($field['value']) && !empty($data[$idx])) {
//console("Using data value", $data[$idx], "for value of field $idx");
$value = $data[$idx];
// Convert data for the list field with autocompletion
if ($field['data-type'] == kolab_form::TYPE_LIST) {
if (!is_array($value)) {
if (!empty($field['data-autocomplete'])) {
$value = array($value => $value);
}
else {
$value = (array) $value;
}
}
$value = !empty($field['data-autocomplete']) ? array_keys($value) : array_values($value);
}
if (is_array($value)) {
$value = implode("\n", $value);
}
$field['value'] = $value;
}
else if ($add_mode && !isset($field['value']) && isset($field['default'])) {
$field['value'] = $field['default'];
unset($field['default']);
}
// @TODO: We assume here that all autocompletion lists are associative
// It's likely that we'll need autocompletion on ordinary lists
if (!empty($field['data-autocomplete'])) {
$assoc_fields[$idx] = !empty($data[$idx]) ? $data[$idx] : array();
}
+
+ if ($field['type'] == kolab_form::INPUT_CHECKBOX) {
+ $field['checked'] = $field['value'] == 'TRUE';
+ }
/*
if (!empty($field['suffix'])) {
$field['suffix'] = kolab_html::escape($this->translate($field['suffix']));
}
*/
if (!empty($field['options'])) {
foreach ($field['options'] as $opt_idx => $option) {
if (is_array($option)) {
$field['options'][$opt_idx]['content'] = kolab_html::escape($this->translate($option['content']));
}
else {
$field['options'][$opt_idx] = kolab_html::escape($this->translate($option));
}
}
}
if (!empty($field['description'])) {
$description = $this->translate($field['description']);
if ($description != $field['description']) {
$field['title'] = $description;
}
unset($field['description']);
}
if (empty($field['name'])) {
$field['name'] = $idx;
}
if (empty($field['readonly']) && empty($field['disabled'])) {
// count writeable fields
if ($field['type'] && $field['type'] != kolab_form::INPUT_HIDDEN) {
$writeable++;
}
if ($idx != "userpassword2") {
if (!empty($field['required'])) {
$req_fields[] = $idx;
}
}
}
//console("Adding field to form", $field);
$form->add_element($field);
}
}
if (!empty($data['section'])) {
$form->activate_section($data['section']);
}
if ($writeable) {
$form->add_button(array(
'value' => kolab_html::escape($this->translate('button.submit')),
'onclick' => "kadm.{$name}_save()",
));
}
if (!empty($data['id']) && in_array('delete', (array) $data['effective_rights']['entry'])) {
$id = $data['id'];
// disable delete for self
if ($id != $_SESSION['user']['id']) {
$form->add_button(array(
'value' => kolab_html::escape($this->translate('button.delete')),
'onclick' => "kadm.{$name}_delete('{$id}')",
));
}
}
$ac_min_len = $this->config_get('autocomplete_min_length', 1, Conf::INT);
$this->output->set_env('form_id', $attribs['id']);
$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->add_translation('form.required.empty', 'form.maxcount.exceeded',
$name . '.add.success', $name . '.edit.success', $name . '.delete.success',
'add', 'edit', 'delete');
return $form;
}
/**
* Search form.
*
* @return string HTML output of the form
*/
public function search_form()
{
$attribs = $this->get_search_attribs();
if (empty($attribs)) {
return '';
}
$options = array();
foreach (array_keys($attribs) as $key) {
$options[$key] = kolab_html::escape($this->translate('search.' . $key));
}
$form = new kolab_form(array('id' => 'search-form'));
$form->add_section('criteria', kolab_html::escape($this->translate('search.criteria')));
$form->add_element(array(
'section' => 'criteria',
'label' => $this->translate('search.field'),
'name' => 'field',
'type' => kolab_form::INPUT_SELECT,
'options' => $options,
));
$form->add_element(array(
'section' => 'criteria',
'label' => $this->translate('search.method'),
'name' => 'method',
'type' => kolab_form::INPUT_SELECT,
'options' => array(
'both' => kolab_html::escape($this->translate('search.contains')),
'exact' => kolab_html::escape($this->translate('search.is')),
'prefix' => kolab_html::escape($this->translate('search.prefix')),
),
));
return $form->output();
}
/**
* Returns list of attributes assigned to search field(s)
*
* @param string $name Optional search field name
*
* @return array List of attributes
*/
protected function get_search_attribs($name = null)
{
$task = $this->get_task();
$types = $this->object_types($task);
$attribs = array();
foreach ($this->search_attribs as $key => $value) {
foreach ((array)$value as $idx => $attr) {
$found = false;
foreach ($types as $type) {
if (array_key_exists($attr, (array)$type['attributes']['auto_form_fields'])
|| array_key_exists($attr, (array)$type['attributes']['form_fields'])
|| array_key_exists($attr, (array)$type['attributes']['fields'])
) {
$found = true;
break;
}
}
if (!$found) {
unset($value[$idx]);
}
}
if (!empty($value)) {
$attribs[$key] = $value;
}
}
return $name ? $attribs[$name] : $attribs;
}
/**
* Object list (and search handler)
*/
public function action_list()
{
if (empty($this->list_attribs)) {
return;
}
$task = $this->get_task();
$page = (int) self::get_input('page', 'POST');
if (!$page || $page < 1) {
$page = 1;
}
// request parameters
$post = array(
'attributes' => $this->list_attribs,
'sort_by' => $this->list_attribs,
// 'sort_order' => 'ASC',
'page_size' => $this->page_size,
'page' => $page,
);
// search parameters
if (!empty($_POST['search'])) {
$search = self::get_input('search', 'POST', true);
$field = self::get_input('field', 'POST');
$method = self::get_input('method', 'POST');
$attrs = $this->get_search_attribs($field);
$search_request = array();
foreach ($attrs as $attr) {
$search_request[$attr] = array(
'value' => $search,
'type' => $method,
);
}
}
else if (!empty($_POST['search_request'])) {
$search_request = self::get_input('search_request', 'POST');
$search_request = @unserialize(base64_decode($search_request));
}
if (!empty($search_request)) {
$post['search'] = $search_request;
$post['search_operator'] = 'OR';
}
// get users list
$module = $this->list_module ? $this->list_module : $task . 's';
$result = $this->api_post($module . '.list', null, $post);
$count = $result->get('count');
$result = (array) $result->get('list');
// calculate records
if ($count) {
$start = 1 + max(0, $page - 1) * $this->page_size;
$end = min($start + $this->page_size - 1, $count);
}
$rows = $head = $foot = array();
$cols = array('name');
$i = 0;
// table header
$head[0]['cells'][] = array('class' => 'name', 'body' => $this->translate($task . '.list'));
// table footer (navigation)
if ($count) {
$pages = ceil($count / $this->page_size);
$prev = max(0, $page - 1);
$next = $page < $pages ? $page + 1 : 0;
$count_str = kolab_html::span(array(
'content' => $this->translate('list.records', $start, $end, $count)), true);
$prev = kolab_html::a(array(
'class' => 'prev' . ($prev ? '' : ' disabled'),
'href' => '#',
'onclick' => $prev ? "kadm.command('$task.list', {page: $prev})" : "return false",
));
$next = kolab_html::a(array(
'class' => 'next' . ($next ? '' : ' disabled'),
'href' => '#',
'onclick' => $next ? "kadm.command('$task.list', {page: $next})" : "return false",
));
$foot_body = kolab_html::span(array('content' => $prev . $count_str . $next));
}
$foot[0]['cells'][] = array('class' => 'listnav', 'body' => $foot_body);
// table body
if (!empty($result)) {
foreach ($result as $idx => $item) {
if (!is_array($item)) {
continue;
}
if (method_exists($this, 'list_item_handler')) {
$item = $this->list_item_handler($item);
}
else {
$item = array_shift($item);
}
if (empty($item)) {
continue;
}
$i++;
$cells = array();
$cells[] = array('class' => 'name', 'body' => kolab_html::escape($item),
'onclick' => "kadm.command('$task.info', '$idx')");
$rows[] = array('id' => $i, 'class' => 'selectable', 'cells' => $cells);
}
}
else {
$rows[] = array('cells' => array(
0 => array('class' => 'empty-body', 'body' => $this->translate($task . '.norecords')
)));
}
$table = kolab_html::table(array(
'id' => $task . 'list',
'class' => 'list',
'head' => $head,
'body' => $rows,
'foot' => $foot,
));
if ($this->action == 'list') {
$this->output->command('set_watermark', 'taskcontent');
}
$this->output->set_env('search_request', $search_request ? base64_encode(serialize($search_request)) : null);
$this->output->set_env('list_page', $page);
$this->output->set_env('list_count', $count);
$this->output->set_env('list_size', count($result));
$this->output->set_object($task . 'list', $table);
}
}
diff --git a/public_html/js/kolab_admin.js b/public_html/js/kolab_admin.js
index fac2aa6..a93d515 100644
--- a/public_html/js/kolab_admin.js
+++ b/public_html/js/kolab_admin.js
@@ -1,2310 +1,2315 @@
/*
+--------------------------------------------------------------------------+
| This file is part of the Kolab Web Admin Panel |
| |
| Copyright (C) 2011-2012, 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 |
| by the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/> |
+--------------------------------------------------------------------------+
| Author: Aleksander Machniak <machniak@kolabsys.com> |
+--------------------------------------------------------------------------+
*/
function kolab_admin()
{
var ref = this;
this.env = {};
this.translations = {};
this.request_timeout = 300;
this.message_time = 3000;
this.events = {};
// 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); }
});
/*********************************************************/
/********* basic utilities *********/
/*********************************************************/
// set environment variable(s)
this.set_env = function(p, value)
{
if (p != null && typeof p === 'object' && !value)
for (var n in p)
this.env[n] = p[n];
else
this.env[p] = value;
};
// add a localized label(s) to the client environment
this.tdef = function(p, value)
{
if (typeof p == 'string')
this.translations[p] = value;
else if (typeof p == 'object')
$.extend(this.translations, p);
};
// return a localized string
this.t = function(label)
{
if (this.translations[label])
return this.translations[label];
else
return label;
};
// print a message into browser console
this.log = function(msg)
{
if (window.console && console.log)
console.log(msg);
};
// execute a specific command on the web client
this.command = function(command, props, obj)
{
if (obj && obj.blur)
obj.blur();
if (this.busy)
return false;
this.set_busy(true, 'loading');
var ret = undefined,
func = command.replace(/[^a-z]/g, '_'),
task = command.replace(/\.[a-z-_]+$/g, '');
if (this[func] && typeof this[func] === 'function') {
ret = this[func](props);
}
else {
this.http_post(command, props);
}
// update menu state
$('li', $('#navigation')).removeClass('active');
$('li.'+task, ('#navigation')).addClass('active');
return ret === false ? false : obj ? false : true;
};
this.set_busy = function(a, message)
{
if (a && message) {
var msg = this.t(message);
if (msg == message)
msg = 'Loading...';
this.display_message(msg, 'loading');
}
else if (!a) {
this.hide_message('loading');
}
this.busy = a;
// if (this.gui_objects.editform)
// this.lock_form(this.gui_objects.editform, a);
// clear pending timer
if (this.request_timer)
clearTimeout(this.request_timer);
// set timer for requests
if (a && this.env.request_timeout)
this.request_timer = window.setTimeout(function() { ref.request_timed_out(); }, this.request_timeout * 1000);
};
// called when a request timed out
this.request_timed_out = function()
{
this.set_busy(false);
this.display_message('Request timed out!', 'error');
};
// Add variable to GET string, replace old value if exists
this.add_url = function(url, name, value)
{
value = urlencode(value);
if (/(\?.*)$/.test(url)) {
var urldata = RegExp.$1,
datax = RegExp('((\\?|&)'+RegExp.escape(name)+'=[^&]*)');
if (datax.test(urldata))
urldata = urldata.replace(datax, RegExp.$2 + name + '=' + value);
else
urldata += '&' + name + '=' + value
return url.replace(/(\?.*)$/, urldata);
}
else
return url + '?' + name + '=' + value;
};
this.trigger_event = function(event, data)
{
if (this.events[event])
for (var i in this.events[event])
this.events[event][i](data);
};
this.add_event_listener = function(event, func)
{
if (!this.events[event])
this.events[event] = [];
this.events[event].push(func);
};
/*********************************************************/
/********* GUI functionality *********/
/*********************************************************/
// write to the document/window title
this.set_pagetitle = function(title)
{
if (title && document.title)
document.title = title;
};
// display a system message (types: loading, notice, error)
this.display_message = function(msg, type, timeout)
{
var obj, ref = this;
if (!type)
type = 'notice';
if (msg)
msg = this.t(msg);
if (type == 'loading') {
timeout = this.request_timeout * 1000;
if (!msg)
msg = this.t('loading');
}
else if (!timeout)
timeout = this.message_time * (type == 'error' || type == 'warning' ? 2 : 1);
obj = $('<div>');
if (type != 'loading') {
msg = '<div><span>' + msg + '</span></div>';
obj.addClass(type).click(function() { return ref.hide_message(); });
}
if (timeout > 0)
window.setTimeout(function() { ref.hide_message(type, type != 'loading'); }, timeout);
obj.attr('id', type == 'loading' ? 'loading' : 'message')
.appendTo('body').html(msg).show();
};
// make a message to disapear
this.hide_message = function(type, fade)
{
if (type == 'loading')
$('#loading').remove();
else
$('#message').fadeOut('normal', function() { $(this).remove(); });
};
this.set_watermark = function(id)
{
if (this.env.watermark)
$('#'+id).html(this.env.watermark);
}
/********************************************************/
/********* Remote request methods *********/
/********************************************************/
// compose a valid url with the given parameters
this.url = function(action, query)
{
var k, param = {},
querystring = typeof query === 'string' ? '&' + query : '';
if (typeof action !== 'string')
query = action;
else if (!query || typeof query !== 'object')
query = {};
// overwrite task name
if (action) {
if (action.match(/^([a-z]+)/i))
query.task = RegExp.$1;
if (action.match(/[^a-z0-9-_]([a-z0-9-_]+)$/i))
query.action = RegExp.$1;
}
// remove undefined values
for (k in query) {
if (query[k] !== undefined && query[k] !== null)
param[k] = query[k];
}
return '?' + $.param(param) + querystring;
};
// send a http POST request to the server
this.http_post = function(action, postdata)
{
var url = this.url(action);
if (postdata && typeof postdata === 'object')
postdata.remote = 1;
else {
if (!postdata)
postdata = '';
postdata += '&remote=1';
}
this.set_request_time();
return $.ajax({
type: 'POST', url: url, data: postdata, dataType: 'json',
success: function(response) { kadm.http_response(response, action); },
error: function(o, status, err) { kadm.http_error(o, status, err); }
});
};
// send a http POST request to the API service
this.api_post = function(action, postdata, func)
{
var url = 'api/' + action;
if (!func) func = 'api_response';
this.set_request_time();
return $.ajax({
type: 'POST', url: url, data: JSON.stringify(postdata), dataType: 'json',
contentType: 'application/json; charset=utf-8',
success: function(response) { kadm[func](response); },
error: function(o, status, err) { kadm.http_error(o, status, err); }
});
};
// handle HTTP response
this.http_response = function(response, action)
{
var i;
if (!response)
return;
// set env vars
if (response.env)
this.set_env(response.env);
// we have translation labels to add
if (typeof response.labels === 'object')
this.tdef(response.labels);
// HTML page elements
if (response.objects)
for (i in response.objects)
$('#'+i).html(response.objects[i]);
this.update_request_time();
this.set_busy(false);
// if we get javascript code from server -> execute it
if (response.exec)
eval(response.exec);
response.action = action;
this.trigger_event('http-response', response);
};
// handle HTTP request errors
this.http_error = function(request, status, err)
{
var errmsg = request.statusText;
this.set_busy(false);
request.abort();
if (request.status && errmsg)
this.display_message(this.t('servererror') + ' (' + errmsg + ')', 'error');
};
this.api_response = function(response)
{
this.update_request_time();
this.set_busy(false);
if (!response || response.status != 'OK') {
// Logout on invalid-session error
if (response && response.code == 403)
this.main_logout();
else
this.display_message(response && response.reason ? response.reason : this.t('servererror'), 'error');
return false;
}
return true;
};
/********************************************************/
/********* 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 *********/
/*********************************************************/
this.ac_init = function(obj, props)
{
obj.keydown(function(e) { return kadm.ac_keydown(e, props); })
.attr('autocomplete', 'off');
};
// handler for keyboard events on autocomplete-fields
this.ac_keydown = function(e, props)
{
if (this.ac_timer)
clearTimeout(this.ac_timer);
var highlight, key = e.which;
switch (key) {
case 38: // arrow up
case 40: // arrow down
if (!this.ac_visible())
break;
var dir = key == 38 ? 1 : 0;
highlight = $('.selected', this.ac_pane).get(0);
if (!highlight)
highlight = this.ac_pane.__ul.firstChild;
if (highlight)
this.ac_select(dir ? highlight.previousSibling : highlight.nextSibling);
return e.stopPropagation();
case 9: // tab
if (e.shiftKey || !this.ac_visible()) {
this.ac_stop();
return;
}
case 13: // enter
if (!this.ac_visible())
return false;
// insert selected item and hide selection pane
this.ac_insert(this.ac_selected);
this.ac_stop();
return e.stopPropagation();
case 27: // escape
this.ac_stop();
return;
case 37: // left
case 39: // right
if (!e.shiftKey)
return;
}
// start timer
this.ac_timer = window.setTimeout(function() { kadm.ac_start(props); }, 500);
this.ac_input = e.target;
return true;
};
this.ac_visible = function()
{
return (this.ac_selected !== null && this.ac_selected !== undefined && this.ac_value);
};
this.ac_select = function(node)
{
if (!node)
return;
var current = $('.selected', this.ac_pane);
if (current.length)
current.removeClass('selected');
$(node).addClass('selected');
this.ac_selected = node._id;
};
// autocomplete search processor
this.ac_start = function(props)
{
var q = this.ac_input ? this.ac_input.value : null,
min = this.env.autocomplete_min_length,
old_value = this.ac_value,
ac = this.ac_data;
if (q === null)
return;
// trim query string
q = $.trim(q);
// Don't (re-)search if the last results are still active
if (q == old_value)
return;
// Stop and destroy last search
this.ac_stop();
if (q.length && q.length < min) {
this.display_message(this.t('search.acchars').replace('$min', min), 'notice', 2000);
return;
}
this.ac_value = q;
// ...string is empty
if (!q.length)
return;
// ...new search value contains old one, but the old result was empty
if (old_value && old_value.length && q.indexOf(old_value) == 0 && this.ac_result && !this.ac_result.length)
return;
var i, xhr, data = props,
action = props && props.action ? props.action : 'form_value.list_options';
this.ac_oninsert = props.oninsert;
data.search = q;
delete data['action'];
delete data['insert_func'];
this.display_message(this.t('search.loading'), 'loading');
xhr = this.api_post(action, data, 'ac_result');
this.ac_data = xhr;
};
this.ac_result = function(response)
{
// search stopped in meantime?
if (!this.ac_value)
return;
if (!this.api_response(response))
return;
// ignore this outdated search response
if (this.ac_input && response.result.search != this.ac_value)
return;
// display search results
var i, ul, li, text,
result = response.result.list,
pos = $(this.ac_input).offset(),
value = this.ac_value,
rx = new RegExp('(' + RegExp.escape(value) + ')', 'ig');
// create results pane if not present
if (!this.ac_pane) {
ul = $('<ul>');
this.ac_pane = $('<div>').attr('id', 'autocompletepane')
.css({ position:'absolute', 'z-index':30000 }).append(ul).appendTo(document.body);
this.ac_pane.__ul = ul[0];
}
ul = this.ac_pane.__ul;
// reset content
ul.innerHTML = '';
// move the results pane right under the input box
this.ac_pane.css({left: (pos.left - 1)+'px', top: (pos.top + this.ac_input.offsetHeight - 1)+'px', display: 'none'});
// add each result line to the list
for (i in result) {
text = result[i];
li = document.createElement('LI');
li.innerHTML = text.replace(rx, '##$1%%').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/##([^%]+)%%/g, '<b>$1</b>');
li.onmouseover = function() { kadm.ac_select(this); };
li.onmouseup = function() { kadm.ac_click(this) };
li._id = i;
ul.appendChild(li);
}
if (ul.childNodes.length) {
this.ac_pane.show();
// select the first
li = $('li:first', ul);
li.addClass('selected');
this.ac_selected = li.get(0)._id;
}
this.env.ac_result = result;
};
this.ac_click = function(node)
{
if (this.ac_input)
this.ac_input.focus();
this.ac_insert(node._id);
this.ac_stop();
};
this.ac_insert = function(id)
{
var val = this.env.ac_result[id];
if (typeof this.ac_oninsert == 'function')
this.ac_oninsert(id, val);
else
$(this.ac_input).val(val);
};
this.ac_blur = function()
{
if (this.ac_timer)
clearTimeout(this.ac_timer);
this.ac_input = null;
this.ac_stop();
};
this.ac_stop = function()
{
this.ac_selected = null;
this.ac_value = '';
if (this.ac_pane)
this.ac_pane.hide();
this.ac_destroy();
};
// Clears autocomplete data/requests
this.ac_destroy = function()
{
if (this.ac_data)
this.ac_data.abort();
this.ac_data = null;
this.ac_info = null;
};
/*********************************************************/
/********* Forms widgets *********/
/*********************************************************/
// Form initialization
this.form_init = function(id)
{
var form = $('#'+id);
this.trigger_event('form-load', id);
// replace some textarea fields with pretty/smart input lists
$('textarea[data-type="list"]', form)
.each(function() { kadm.form_list_element_wrapper(this); });
// create smart select fields
$('input[data-type="select"]', form)
.each(function() { kadm.form_select_element_wrapper(this); });
// create LDAP URL fields
$('input[data-type="ldap_url"]:not(:disabled):not([readonly])', form)
.each(function() { kadm.form_url_element_wrapper(this); });
};
// Form serialization
this.form_serialize = function(data)
{
var form = $(data.id);
// smart list fields
$('textarea[data-type="list"]:not(:disabled)', form).each(function() {
var i, v, value = [],
re = RegExp('^' + RegExp.escape(this.name) + '\[[0-9-]+\]$');
for (i in data.json) {
if (i.match(re)) {
if (v = $('input[name="'+i+'"]', form).val())
value.push(v);
delete data.json[i];
}
}
// autocompletion lists data is stored in env variable
if (kadm.env.assoc_fields[this.name]) {
value = [];
for (i in kadm.env.assoc_fields[this.name])
value.push(i);
}
data.json[this.name] = value;
});
// smart selects
$('input[data-type="select"]', form).each(function() {
delete data.json[this.name];
});
// LDAP URL fields
$('input[data-type="ldap_url"]:not(:disabled):not([readonly])', form).each(function() {
data.json = kadm.form_url_element_submit(this.name, data.json, form);
});
// quota inputs
$('input[data-type="quota"]', form).each(function() {
var unit = $('select[name="' + this.name + '-unit"]').val();
if (unit && this.value)
data.json[this.name] = this.value + unit;
delete data.json[this.name + '-unit'];
});
+ // checkbox inputs
+ $('input[type="checkbox"]', form).each(function() {
+ data.json[this.name] = this.checked ? 'TRUE' : 'FALSE';
+ });
+
return data;
};
// Form element update handler
this.form_element_update = function(data)
{
var elem = $('[name="'+data.name+'"]');
if (!elem.length)
return;
if (elem.attr('data-type') == 'list') {
// remove old wrapper
$('span[class="listarea"]', elem.parent()).remove();
// insert new list element
this.form_list_element_wrapper(elem.get(0));
}
};
// Replaces form element with smart list element
this.form_list_element_wrapper = function(form_element)
{
var i = 0, j = 0, list = [], elem, e = $(form_element),
form = form_element.form,
disabled = e.attr('disabled'),
readonly = e.attr('readonly'),
autocomplete = e.attr('data-autocomplete'),
maxlength = e.attr('data-maxlength'),
area = $('<span class="listarea"></span>');
e.hide();
if (autocomplete)
list = this.env.assoc_fields ? this.env.assoc_fields[form_element.name] : [];
else if (form_element.value)
list = form_element.value.split("\n");
// Need at least one element
if (!autocomplete || disabled || readonly) {
$.each(list, function() { i++; });
if (!i)
list = [''];
}
// Create simple list for readonly/disabled
if (disabled || readonly) {
area.addClass('readonly');
// add simple input rows
$.each(list, function(i, v) {
var elem = $('<input>');
elem.attr({
value: v,
disabled: disabled,
readonly: readonly,
name: form_element.name + '[' + (j++) + ']'
})
elem = $('<span class="listelement">').append(elem);
elem.appendTo(area);
});
}
// extended widget with add/remove buttons and/or autocompletion
else {
// add autocompletion input
if (autocomplete) {
elem = this.form_list_element(form, {
maxlength: maxlength,
autocomplete: autocomplete,
element: e
}, -1);
// Initialize autocompletion
var props = {attribute: form_element.name, oninsert: this.form_element_oninsert};
if (i = $('[name="type_id"]', form).val())
props.type_id = i;
if (i = $('[name="object_type"]', form).val())
props.object_type = i;
if (i = $('[name="id"]', form).val())
props.id = i;
this.ac_init(elem, props);
elem.appendTo(area);
area.addClass('autocomplete');
}
// add input rows
$.each(list, function(i, v) {
var elem = kadm.form_list_element(form, {
value: v,
key: i,
maxlength: maxlength,
autocomplete: autocomplete,
element: e
}, j++);
elem.appendTo(area);
});
}
area.appendTo(form_element.parentNode);
};
// Creates smart list element
this.form_list_element = function(form, data, idx)
{
var content, elem, input,
key = data.key,
orig = data.element
ac = data.autocomplete;
data.name = (orig ? orig.attr('name') : data.name) + '[' + idx + ']';
data.readonly = (ac && idx >= 0);
// remove internal attributes
delete data['element'];
delete data['autocomplete'];
delete data['key'];
// build element content
content = '<span class="listelement"><span class="actions">'
+ (!ac ? '<span title="" class="add"></span>' : ac && idx == -1 ? '<span title="" class="search"></span>' : '')
+ (!ac || idx >= 0 ? '<span title="" class="reset"></span>' : '')
+ '</span><input type="text" autocomplete="off"></span>';
elem = $(content);
input = $('input', elem);
// Set INPUT attributes
input.attr(data);
if (data.readonly)
input.addClass('readonly');
if (ac)
input.addClass('autocomplete');
// attach element creation event
if (!ac)
$('span[class="add"]', elem).click(function() {
var name = data.name.replace(/\[[0-9]+\]$/, ''),
span = $(this.parentNode.parentNode),
maxcount = $('textarea[name="'+name+'"]').attr('data-maxcount');
// check element count limit
if (maxcount && maxcount <= span.parent().children().length) {
alert(kadm.t('form.maxcount.exceeded'));
return;
}
var dt = (new Date()).getTime(),
elem = kadm.form_list_element(form, {name: name}, dt);
kadm.ac_stop();
span.after(elem);
$('input', elem).focus();
});
// attach element deletion event
if (!ac || idx >= 0)
$('span[class="reset"]', elem).click(function() {
var span = $(this.parentNode.parentNode),
name = data.name.replace(/\[[0-9]+\]$/, ''),
l = $('input[name^="' + name + '"]', form),
key = $(this).data('key');
if (l.length > 1 || $('input[name="' + name + '"]', form).attr('data-autocomplete'))
span.remove();
else
$('input', span).val('').focus();
// delete key from internal field representation
if (key !== undefined && kadm.env.assoc_fields[name])
delete kadm.env.assoc_fields[name][key];
kadm.ac_stop();
}).data('key', key);
return elem;
};
this.form_element_oninsert = function(key, val)
{
var elem, input = $(this.ac_input).get(0),
dt = (new Date()).getTime(),
span = $(input.parentNode),
name = input.name.replace(/\[-1\]$/, ''),
af = kadm.env.assoc_fields,
maxcount = $('textarea[name="'+name+'"]').attr('data-maxcount');
// reset autocomplete input
input.value = '';
// check element count limit
if (maxcount && maxcount <= span.parent().children().length - 1) {
alert(kadm.t('form.maxcount.exceeded'));
return;
}
// check if element doesn't exist on the list already
if (!af[name])
af[name] = {};
if (af[name][key])
return;
// add element
elem = kadm.form_list_element(input.form, {
name: name,
autocomplete: true,
value: val
}, dt);
span.after(elem);
// update field variable
af[name][key] = val;
};
// Replaces form element with smart select element
this.form_select_element_wrapper = function(form_element)
{
var e = $(form_element),
form = form_element.form,
name = form_element.name,
elem = $('#selectlabel_' + name),
area = $('<span class="listarea autocomplete select popup" id="selectarea_' + name + '"></span>'),
content = $('<span class="listcontent" id="selectcontent_' + name + '"></span>'),
list = this.env.assoc_fields ? this.env.assoc_fields[form_element.name] : [];
if (elem.length) {
$('#selectarea_' + name).remove();
$('#selectcontent_' + name).remove();
}
else {
elem = $('<span class="link" id="selectlabel_' + name + '"></span>')
.css({cursor: 'pointer'})
.click(function(e) {
var popup = $('span.listarea', this.parentNode);
kadm.popup_show(e, popup);
$('input', popup).val('').focus();
$('span.listcontent > span.listelement', popup).removeClass('selected').show();
})
.appendTo(form_element.parentNode);
}
elem.text(e.val());
if (list.length <= 1)
return;
if (form_element.type != 'hidden') e.hide();
elem = this.form_list_element(form, {
autocomplete: true,
element: e
}, -1);
elem.appendTo(area);
content.appendTo(area);
area.hide().appendTo(form_element.parentNode);
// popup events
$('input', area)
.click(function(e) {
// stop click on the popup
e.stopPropagation();
})
.keypress(function(e) {
// prevent form submission with Enter key
if (e.which == 13)
e.preventDefault();
})
.keydown(function(e) {
// block Up/Down arrow keys,
// in Firefox Up arrow moves cursor left
if (e.which == 38 || e.which == 40)
e.preventDefault();
})
.keyup(function(e) {
// filtering
var s = this.value,
options = $('span.listcontent > span.listelement', area);
// Enter key
if (e.which == 13) {
options.filter('.selected').click()
return;
}
// Escape
else if (e.which == 27) {
area.hide();
this.value = s = '';
}
// UP/Down arrows
else if (e.which == 38 || e.which == 40) {
options = options.not(':hidden');
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;
};
// Replaces form element with LDAP URL element
this.form_url_element_wrapper = function(form_element)
{
var i, e = $(form_element),
form = form_element.form,
name = form_element.name,
ldap = this.parse_ldap_url(e.val()) || {},
options = ['sub', 'one', 'base'],
div = $('<div class="ldap_url"><table></table></div>'),
host = $('<input type="text">').attr({name: 'ldap_host_'+name, size: 30, value: ldap.host, 'class': 'ldap_host'}),
port = $('<input type="text">').attr({name: 'ldap_port_'+name, size: 5, value: ldap.port || '389'}),
base = $('<input type="text">').attr({name: 'ldap_base_'+name, value: ldap.base}),
scope = $('<select>').attr({name: 'ldap_scope_'+name}),
filter = $('<ul>'),
row_host = $('<tr class="ldap_host"><td></td><td></td></tr>'),
row_base = $('<tr class="ldap_base"><td></td><td></td></tr>'),
row_scope = $('<tr class="ldap_scope"><td></td><td></td></tr>'),
row_filter = $('<tr class="ldap_filter"><td></td><td></td></tr>');
for (i in options)
$('<option>').val(options[i]).text(this.t('ldap.'+options[i])).appendTo(scope);
scope.val(ldap.scope);
for (i in ldap.filter)
filter.append(this.form_url_filter_element(name, i, ldap.filter[i]));
if (!$('li', filter).length)
filter.append(this.form_url_filter_element(name, 0, {}));
e.hide();
$('td:first', row_host).text(this.t('ldap.host'));
$('td:last', row_host).append(host).append($('<span>').text(':')).append(port);
$('td:first', row_base).text(this.t('ldap.basedn'));
$('td:last', row_base).append(base);
$('td:first', row_scope).text(this.t('ldap.scope'));
$('td:last', row_scope).append(scope);
$('td:first', row_filter).text(this.t('ldap.conditions'));
$('td:last', row_filter).append(filter);
$('table', div).append(row_host).append(row_base).append(row_scope).append(row_filter);
$(form_element).parent().append(div);
};
this.form_url_filter_element = function(name, idx, filter)
{
var options = ['any', 'both', 'prefix', 'suffix', 'exact'],
filter_type = $('<select>').attr({name: 'ldap_filter_type_'+name+'['+idx+']'}),
filter_name = $('<input type="text">').attr({name: 'ldap_filter_name_'+name+'['+idx+']'}),
filter_value = $('<input type="text">').attr({name: 'ldap_filter_value_'+name+'['+idx+']'}),
a_add = $('<a class="button add" href="#add"></a>').click(function() {
var dt = new Date().getTime();
$(this.parentNode.parentNode).append(kadm.form_url_filter_element(name, dt, {}));
}).attr({title: this.t('add')}),
a_del = $('<a class="button delete" href="#delete"></a>').click(function() {
if ($('li', this.parentNode.parentNode).length > 1)
$(this.parentNode).remove();
else {
$('input', this.parentNode).val('');
$('select', this.parentNode).val('any').change();
}
}).attr({title: this.t('delete')}),
li = $('<li>');
for (i in options)
$('<option>').val(options[i]).text(this.t('ldap.filter_'+options[i])).appendTo(filter_type);
if (filter.type)
filter_type.val(filter.type);
if (filter.name)
filter_name.val(filter.name);
if (filter.value)
filter_value.val(filter.value);
filter_type.change(function() {
filter_value.css({display: $(this).val() == 'any' ? 'none' : 'inline'});
}).change();
return li.append(filter_name).append(filter_type).append(filter_value)
.append(a_del).append(a_add);
};
// updates form data with LDAP URL (on form submit)
this.form_url_element_submit = function(name, data, form)
{
var i, rx = new RegExp('^ldap_(host|port|base|scope|filter_name|filter_type|filter_value)_'+name+'(\\[|$)');
for (i in data)
if (rx.test(i))
delete data[i];
data[name] = this.form_url_element_save(name, form);
return data;
};
// updates LDAP URL field
this.form_url_element_save = function(name, form)
{
var url, form = $(form), params = {
host: $('input[name="ldap_host_'+name+'"]', form).val(),
port: $('input[name="ldap_port_'+name+'"]', form).val(),
base: $('input[name="ldap_base_'+name+'"]', form).val(),
scope: $('select[name="ldap_scope_'+name+'"]', form).val(),
filter: []};
$('input[name^="ldap_filter_name_'+name+'"]', form).each(function() {
if (this.value && /\[([^\]]+)\]/.test(this.name)) {
var suffix = name + '[' + RegExp.$1 + ']',
type = $('select[name="ldap_filter_type_'+suffix+'"]', form).val(),
value = type == 'any' ? '' : $('input[name="ldap_filter_value_'+suffix+'"]', form).val();
params.filter.push({name: this.value, type: type, value: value, join: 'AND', level: 0});
}
});
url = this.build_ldap_url(params);
$('input[name="'+name+'"]').val(url);
return url;
};
/*********************************************************/
/********* Forms *********/
/*********************************************************/
this.serialize_form = function(id)
{
var i, v, json = {},
form = $(id),
query = form.serializeArray(),
extra = this.env.extra_fields || [];
for (i in query)
json[query[i].name] = query[i].value;
// read extra (disabled) fields
for (i=0; i<extra.length; i++)
if (v = $('[name="'+extra[i]+'"]', form).val())
json[extra[i]] = v;
// serializeArray() doesn't work properly for multi-select
$('select[multiple="multiple"]', form).each(function() {
var name = this.name;
json[name] = [];
$(':selected', this).each(function() {
json[name].push(this.value);
});
});
this.form_serialize({id: id, json: json});
return json;
};
this.form_value_change = function(events)
{
var i, j, e, elem, name, elem_name,
form = $('#'+this.env.form_id),
id = $('[name="id"]', form).val(),
type_id = $('[name="type_id"]', form).val(),
object_type = $('[name="object_type"]', form).val(),
data = {type_id: type_id, object_type: object_type, attributes: []};
if (id)
data.id = id;
this.set_busy(true, 'loading');
for (i=0; i<events.length; i++) {
name = events[i];
e = this.env.auto_fields[name];
if (!e)
continue;
data.attributes.push(name);
for (j=0; j<e.data.length; j++) {
elem_name = e.data[j];
if (!data[elem_name] && (elem = $('[name="'+elem_name+'"]', form)))
data[elem_name] = elem.val();
}
}
this.api_post('form_value.generate', data, 'form_value_response');
this.set_busy(false);
};
this.form_value_response = function(response)
{
var i, val, field;
if (!this.api_response(response))
return;
for (i in response.result) {
val = response.result[i];
field = $('[name="'+i+'"]');
// @TODO: indexed list support
if ($.isArray(val))
val = field.is('textarea') ? val.join("\n") : val.shift();
field.val(val);
this.form_element_update({name: i});
}
};
this.form_value_error = function(name)
{
$('[name="'+name+'"]', $('#'+this.env.form_id)).addClass('error');
}
this.form_error_clear = function()
{
$('input,textarea', $('#'+this.env.form_id)).removeClass('error');
}
this.check_required_fields = function(data)
{
var i, n, is_empty, ret = true,
req_fields = this.env.required_fields;
for (i=0; i<req_fields.length; i++) {
n = req_fields[i];
is_empty = 0;
if ($.isArray(data[n]))
is_empty = (data[n].length == 0) ? 1 : 0;
else
is_empty = !data[n];
if (is_empty) {
this.form_value_error(n);
ret = false;
}
}
return ret;
};
/*********************************************************/
/********* Client commands *********/
/*********************************************************/
this.main_logout = function(params)
{
location.href = '?task=main&action=logout' + (params ? '&' + $.param(params) : '');
return false;
};
this.domain_info = function(id)
{
this.http_post('domain.info', {id: id});
};
this.domain_list = function(props)
{
this.list_handler('domain', props);
};
this.domain_delete = function(id)
{
this.set_busy(true, 'deleting');
this.api_post('domain.delete', {'id': id}, 'domain_delete_response');
};
this.domain_save = function(reload, section)
{
var data = this.serialize_form('#'+this.env.form_id),
action = data.id ? 'edit' : 'add';
if (reload) {
data.section = section;
this.http_post('domain.' + action, {data: data});
return;
}
this.form_error_clear();
if (!this.check_required_fields(data)) {
this.display_message('form.required.empty', 'error');
return;
}
this.set_busy(true, 'saving');
this.api_post('domain.' + action, data, 'domain_' + action + '_response');
};
this.domain_delete_response = function(response)
{
this.response_handler(response, 'domain.delete', 'domain.list', {refresh: 1});
};
this.domain_add_response = function(response)
{
this.response_handler(response, 'domain.add', 'domain.list', {refresh: 1});
};
this.domain_edit_response = function(response)
{
this.response_handler(response, 'domain.edit', 'domain.list', {refresh: 1});
};
this.user_info = function(id)
{
this.http_post('user.info', {'id': id});
};
this.user_list = function(props)
{
this.list_handler('user', props);
};
this.user_delete = function(id)
{
this.set_busy(true, 'deleting');
this.api_post('user.delete', {'id': id}, 'user_delete_response');
};
this.user_save = function(reload, section)
{
var data = this.serialize_form('#'+this.env.form_id),
action = data.id ? 'edit' : 'add';
if (reload) {
data.section = section;
this.http_post('user.' + action, {data: data});
return;
}
this.form_error_clear();
// check password
if (data.userpassword != data.userpassword2) {
this.display_message('user.password.mismatch', 'error');
this.form_value_error('userpassword2');
return;
}
delete data['userpassword2'];
if (!this.check_required_fields(data)) {
this.display_message('form.required.empty', 'error');
return;
}
this.set_busy(true, 'saving');
this.api_post('user.' + action, data, 'user_' + action + '_response');
};
this.user_delete_response = function(response)
{
this.response_handler(response, 'user.delete', 'user.list');
};
this.user_add_response = function(response)
{
this.response_handler(response, 'user.add', 'user.list');
};
this.user_edit_response = function(response)
{
this.response_handler(response, 'user.edit', 'user.list');
};
this.group_info = function(id)
{
this.http_post('group.info', {id: id});
};
this.group_list = function(props)
{
this.list_handler('group', props);
};
this.group_delete = function(id)
{
this.set_busy(true, 'deleting');
this.api_post('group.delete', {'id': id}, 'group_delete_response');
};
this.group_save = function(reload, section)
{
var data = this.serialize_form('#'+this.env.form_id),
action = data.id ? 'edit' : 'add';
if (reload) {
data.section = section;
this.http_post('group.' + action, {data: data});
return;
}
this.form_error_clear();
if (!this.check_required_fields(data)) {
this.display_message('form.required.empty', 'error');
return;
}
this.set_busy(true, 'saving');
this.api_post('group.' + action, data, 'group_' + action + '_response');
};
this.group_delete_response = function(response)
{
this.response_handler(response, 'group.delete', 'group.list');
};
this.group_add_response = function(response)
{
this.response_handler(response, 'group.add', 'group.list');
};
this.group_edit_response = function(response)
{
this.response_handler(response, 'group.edit', 'group.list');
};
this.resource_info = function(id)
{
this.http_post('resource.info', {id: id});
};
this.resource_list = function(props)
{
this.list_handler('resource', props);
};
this.resource_delete = function(id)
{
this.set_busy(true, 'deleting');
this.api_post('resource.delete', {'id': id}, 'resource_delete_response');
};
this.resource_save = function(reload, section)
{
var data = this.serialize_form('#'+this.env.form_id),
action = data.id ? 'edit' : 'add';
if (reload) {
data.section = section;
this.http_post('resource.' + action, {data: data});
return;
}
this.form_error_clear();
if (!this.check_required_fields(data)) {
this.display_message('form.required.empty', 'error');
return;
}
this.set_busy(true, 'saving');
this.api_post('resource.' + action, data, 'resource_' + action + '_response');
};
this.resource_delete_response = function(response)
{
this.response_handler(response, 'resource.delete', 'resource.list');
};
this.resource_add_response = function(response)
{
this.response_handler(response, 'resource.add', 'resource.list');
};
this.resource_edit_response = function(response)
{
this.response_handler(response, 'resource.edit', 'resource.list');
};
this.role_info = function(id)
{
this.http_post('role.info', {id: id});
};
this.role_list = function(props)
{
this.list_handler('role', props);
};
this.role_delete = function(id)
{
this.set_busy(true, 'deleting');
this.api_post('role.delete', {'id': id}, 'role_delete_response');
};
this.role_save = function(reload, section)
{
var data = this.serialize_form('#'+this.env.form_id),
action = data.id ? 'edit' : 'add';
if (reload) {
data.section = section;
this.http_post('role.' + action, {data: data});
return;
}
this.form_error_clear();
if (!this.check_required_fields(data)) {
this.display_message('form.required.empty', 'error');
return;
}
this.set_busy(true, 'saving');
this.api_post('role.' + action, data, 'role_' + action + '_response');
};
this.role_delete_response = function(response)
{
this.response_handler(response, 'role.delete', 'role.list');
};
this.role_add_response = function(response)
{
this.response_handler(response, 'role.add', 'role.list');
};
this.role_edit_response = function(response)
{
this.response_handler(response, 'role.edit', 'role.list');
};
this.sharedfolder_info = function(id)
{
this.http_post('sharedfolder.info', {id: id});
};
this.sharedfolder_list = function(props)
{
this.list_handler('sharedfolder', props);
};
this.sharedfolder_delete = function(id)
{
this.set_busy(true, 'deleting');
this.api_post('sharedfolder.delete', {'id': id}, 'sharedfolder_delete_response');
};
this.sharedfolder_save = function(reload, section)
{
var data = this.serialize_form('#'+this.env.form_id),
action = data.id ? 'edit' : 'add';
if (reload) {
data.section = section;
this.http_post('sharedfolder.' + action, {data: data});
return;
}
this.form_error_clear();
if (!this.check_required_fields(data)) {
this.display_message('form.required.empty', 'error');
return;
}
this.set_busy(true, 'saving');
this.api_post('sharedfolder.' + action, data, 'sharedfolder_' + action + '_response');
};
this.sharedfolder_delete_response = function(response)
{
this.response_handler(response, 'sharedfolder.delete', 'sharedfolder.list');
};
this.sharedfolder_add_response = function(response)
{
this.response_handler(response, 'sharedfolder.add', 'sharedfolder.list');
};
this.sharedfolder_edit_response = function(response)
{
this.response_handler(response, 'sharedfolder.edit', 'sharedfolder.list');
};
this.settings_type_info = function(id)
{
this.http_post('settings.type_info', {id: id});
};
this.settings_type_add = function()
{
this.http_post('settings.type_add', {type: $('#type_list_filter').val()});
};
this.settings_type_list = function(props)
{
if (!props)
props = {};
if (props.search === undefined && this.env.search_request)
props.search_request = this.env.search_request;
props.type = $('#type_list_filter').val();
this.http_post('settings.type_list', props);
};
this.type_delete = function(id)
{
this.set_busy(true, 'deleting');
this.api_post('type.delete', this.type_id_parse(id), 'type_delete_response');
};
this.type_save = function(reload, section)
{
var i, n, attr, request = {},
data = this.serialize_form('#'+this.env.form_id),
action = data.id ? 'edit' : 'add',
required = this.env.attributes_required || [];
if (reload) {
data.section = section;
this.http_post('type.' + action, {data: data});
return;
}
this.form_error_clear();
if (!this.check_required_fields(data)) {
this.display_message('form.required.empty', 'error');
return;
}
if (data.key.match(/[^a-z_-]/)) {
this.display_message('attribute.key.invalid', 'error');
return;
}
request.id = data.id;
request.key = data.key;
request.name = data.name;
request.type = data.type;
request.description = data.description;
request.used_for = data.used_for;
request.attributes = {fields: {}, form_fields: {}, auto_form_fields: {}};
request.attributes.fields.objectclass = data.objectclass;
// Build attributes array compatible with the API format
// @TODO: use attr_table format
for (i in this.env.attr_table) {
// attribute doesn't exist in specified object classes set
if (!(n = this.env.attributes[i]))
continue;
// check required attributes
if (required.length)
required = $.map(required, function(a) { return a != n ? a : null; });
attr = this.env.attr_table[i];
data = {};
if (attr.valtype == 'static') {
request.attributes.fields[i] = attr.data;
continue;
}
if (attr.type == 'list-autocomplete') {
data.type = 'list';
data.autocomplete = true;
}
else if (attr.type != 'text')
data.type = attr.type;
if ((attr.type == 'select' || attr.type == 'multiselect') && attr.values && attr.values.length)
data.values = attr.values;
if (attr.optional)
data.optional = true;
if (attr.maxcount)
data.maxcount = attr.maxcount;
if (attr.validate != 'default')
data.validate = attr.validate;
if (attr['default'] && attr.valtype == 'normal' && attr.type.match(/^(text|select)/))
data['default'] = attr['default'];
if (attr.valtype == 'normal' || attr.valtype == 'auto')
request.attributes.form_fields[i] = data;
if (attr.valtype == 'auto' || attr.valtype == 'auto-readonly') {
if (attr.data)
data.data = attr.data.split(/,/);
request.attributes.auto_form_fields[i] = data;
}
}
if (required.length) {
this.display_message(this.t('attribute.required.error').replace(/\$1/, required.join(',')), 'error');
return;
}
this.set_busy(true, 'saving');
this.api_post('type.' + action, request, 'type_' + action + '_response');
};
this.type_delete_response = function(response)
{
this.response_handler(response, 'type.delete', 'settings.type_list');
};
this.type_add_response = function(response)
{
this.response_handler(response, 'type.add', 'settings.type_list');
};
this.type_edit_response = function(response)
{
this.response_handler(response, 'type.edit', 'settings.type_list');
};
/*********************************************************/
/********* Various helper methods *********/
/*********************************************************/
// universal API response handler
this.response_handler = function(response, action, list, list_params)
{
if (!this.api_response(response))
return;
this.display_message(action + '.success');
var page = this.env.list_page,
list_id = list.replace(/[^a-z]/g, '');
// if objects list exists
if ($('#'+list_id).length) {
// goto previous page if last record on the current page has been deleted
if (this.env.list_page > 1 && this.env.list_size == 1 && action.match(/\.delete/))
page -= 1;
if (!list_params)
list_params = {};
list_params.page = page;
this.command(list, list_params);
this.set_watermark('taskcontent');
}
};
// universal list request handler
this.list_handler = function(type, props)
{
if (!props)
props = {};
if (props.search === undefined && this.env.search_request)
props.search_request = this.env.search_request;
this.http_post(type + '.list', props);
};
// Parses object type identifier
this.type_id_parse = function(id)
{
var id = String(id).split(':');
return {type: id[0], id: id[1]};
};
// Removes attribute row
this.type_attr_delete = function(attr)
{
$('#attr_table_row_' + attr).remove();
$('select[name="attr_name"] > option[value="'+attr+'"]').show();
delete this.env.attr_table[attr];
this.type_attr_cancel();
};
// Displays attribute edition form
this.type_attr_edit = function(attr)
{
var form = $('#type_attr_form');
form.detach();
$('#attr_table_row_'+attr).after(form);
this.type_attr_form_init(attr);
form.slideDown(400);
};
// Displays attribute addition form
this.type_attr_add = function()
{
var form = $('#type_attr_form');
form.detach();
$('#type_attr_table > tbody').append(form);
this.type_attr_form_init();
form.slideDown(400);
};
// Saves attribute form, create/update attribute row
this.type_attr_save = function()
{
var attr, row, data = {},
form_data = this.serialize_form('#'+this.env.form_id),
name_select = $('select[name="attr_name"]');
// read attribute form data
data.type = form_data.attr_type;
data.valtype = form_data.attr_value;
data.optional = form_data.attr_optional;
data.validate = form_data.attr_validate;
data.data = data.valtype != 'normal' ? form_data.attr_data : null;
data.maxcount = data.type == 'list' || data.type == 'list-autocomplete' ? form_data.attr_maxcount : 0;
data.values = data.type == 'select' || data.type == 'multiselect' ? form_data.attr_options : [];
if (name_select.is(':visible')) {
// new attribute
attr = name_select.val();
row = $('<tr><td class="name"></td><td class="type"></td><td class="readonly"></td>'
+'<td class="optional"></td><td class="validate"></td><td class="actions">'
+'<a class="button delete" title="delete" onclick="kadm.type_attr_delete(\''+attr+'\')" href="#delete"></a>'
+'<a class="button edit" title="edit" onclick="kadm.type_attr_edit(\''+attr+'\')" href="#edit"></a></td></tr>')
.attr('id', 'attr_table_row_' + attr).appendTo('#type_attr_table > tbody');
}
else {
// edited attribute
attr = $('span', name_select.parent()).text().toLowerCase();
row = $('#attr_table_row_' + attr);
}
if (data.valtype != 'normal') {
row.attr('title', this.t('attribute.value.' + (data.valtype == 'static' ? 'static' : 'auto')) + ': ' + data.data);
}
if (form_data.attr_default && data.valtype == 'normal' && data.type.match(/^(text|select)/)) {
data['default'] = form_data.attr_default;
}
// Update table row
$('td.name', row).text(this.env.attributes[attr]);
$('td.type', row).text(data.type);
$('td.readonly', row).text(data.valtype == 'auto-readonly' ? this.env.yes_label : this.env.no_label);
$('td.optional', row).text(data.optional ? this.env.yes_label : this.env.no_label);
$('td.validate', row).text(this.t('attribute.validate.' + data.validate));
// Update env data
this.env.attr_table[attr] = data;
this.type_attr_cancel();
};
// Hide attribute form
this.type_attr_cancel = function()
{
$('#type_attr_form').hide();
};
this.type_attr_form_init = function(attr)
{
var name_select = $('select[name="attr_name"]'),
data = attr ? this.env.attr_table[attr] : {},
type = data.type || 'text';
$('select[name="attr_type"]').val(type);
$('select[name="attr_value"]').val(attr ? data.valtype : 'normal');
$('select[name="attr_validate"]').val(attr ? data.validate : '');
$('input[name="attr_default"]').val(data['default'] || '');
$('input[name="attr_optional"]').attr('checked', attr ? data.optional : false);
$('input[name="attr_data"]').val(data.data || '');
$('input[name="attr_maxcount"]').val(data.maxcount || '');
$('textarea[name="attr_options"]').val(data.values ? data.values.join("\n") : '');
$('span', name_select.parent()).remove();
if (attr) {
name_select.hide().val(attr);
$('<span></span>').text(this.env.attributes[attr] ? this.env.attributes[attr] : attr)
.appendTo(name_select.parent());
}
else {
this.type_attr_select_init();
name_select.show();
}
this.form_element_update({name: 'attr_options'});
this.type_attr_type_change();
this.type_attr_value_change();
};
// Initialize attribute name selector
this.type_attr_select_init = function()
{
var select = $('select[name="attr_name"]'),
options = $('option', select);
options.each(function() {
if (kadm.env.attr_table[this.value])
$(this).attr('disabled', true);
});
options.not(':disabled').first().attr('selected', true);
};
// Update attribute form on attribute name change
this.type_attr_name_change = function(elem)
{
this.type_attr_value_change();
};
// Update attribute form on value type change
this.type_attr_value_change = function(elem)
{
var type = $(elem || 'select[name="attr_value"]').val(),
field_type = $('select[name="attr_type"]').val(),
optional = $('#attr_form_row_optional'),
select = $('select[name="attr_name"]').val(),
attr_name = this.env.attributes[select],
// only non-static and non-required attributes can be marked as optional
opt = type != 'static' && $.inArray(attr_name, this.env.attributes_required) == -1;
$('input[name="attr_data"]')[type != 'normal' ? 'show' : 'hide']();
$('#attr_form_row_readonly')[type != 'static' ? 'show' : 'hide']();
$('#attr_form_row_default')[type == 'normal' && field_type.match(/^(text|select)/) ? 'show' : 'hide']();
optional[opt ? 'show' : 'hide']();
if (!opt)
$('input', optional).attr('checked', false);
};
// Update attribute form on type change
this.type_attr_type_change = function(elem)
{
var type = $(elem || 'select[name="attr_type"]').val(),
val_type = $('select[name="attr_value"]').val();
$('#attr_form_row_maxcount')[type == 'list' || type == 'list-autocomplete' ? 'show' : 'hide']();
$('#attr_form_row_options')[type == 'select' || type == 'multiselect' ? 'show' : 'hide']();
$('#attr_form_row_default')[val_type == 'normal' && type.match(/^(text|select)/) ? 'show' : 'hide']();
};
// Update attributes list on object classes change
this.type_attr_class_change = function(field)
{
var data = {attributes: 'attribute', classes: this.type_object_classes(field)};
this.api_post('form_value.select_options', data, 'type_attr_class_change_response');
this.type_attr_cancel();
};
// Update attributes list on object classes change - API response handler
this.type_attr_class_change_response = function(response)
{
if (!this.api_response(response))
return;
var i, lc, list = response.result.attribute.list || [],
required = response.result.attribute.required || [],
select = $('select[name="attr_name"]');
// remove objectClass from attributes list(s)
required = $.map(required, function(a) { return a == 'objectClass' ? null : a; });
list = $.map(list, function(a) { return a == 'objectClass' ? null : a; });
this.env.attributes = {};
this.env.attributes_required = required;
select.empty();
for (i in list) {
lc = list[i].toLowerCase();
this.env.attributes[lc] = list[i];
$('<option>').text(list[i]).val(lc).appendTo(select);
}
};
// Return selected objectclasses array
this.type_object_classes = function(field)
{
var classes = [];
$('option:selected', $(field)).each(function() {
classes.push(this.value);
});
return classes;
};
// Password generation - request
this.generate_password = function(fieldname)
{
this.env.password_field = fieldname;
this.set_busy(true, 'loading');
// we can send only 'attributes' here, because password generation doesn't require object type name/id
this.api_post('form_value.generate', {attributes: [fieldname]}, 'generate_password_response');
};
// Password generation - response handler
this.generate_password_response = function(response)
{
if (!this.api_response(response))
return;
var f = this.env.password_field, pass = response.result[f];
$('input[name="' + f + '"]').val(pass);
$('input[name="' + f + '2"]').val(pass);
};
// LDAP URL parser
this.parse_ldap_url = function(url)
{
var result = {},
url_parser = /^([^:]+):\/\/([^\/]*)\/([^?]*)\?([^?]*)\?([^?]*)\?(.*)$/;
matches = url_parser.exec(url);
if (matches && matches[1])
return {
scheme: matches[1],
host: matches[2],
base: matches[3],
attrs: matches[4],
scope: matches[5],
filter: this.parse_ldap_filter(matches[6])
};
};
// LDAP filter parser
this.parse_ldap_filter = function(filter)
{
var chr, next, elem, pos = 0, join, level = -1, res = [],
len = filter ? filter.length : 0;
if (!filter.match(/^\(.*\)$/))
filter = '(' + filter + ')';
while (len > pos) {
chr = filter.charAt(pos);
if (chr == '&') {
join = 'AND';
level++;
}
else if (chr == '|') {
join = 'OR';
level++;
}
else if (chr == '(') {
next = filter.charAt(pos+1);
if (next != '&' && next != '|') {
next = filter.indexOf(')', pos);
if (next > 0) {
if (elem = this.parse_ldap_filter_entry(filter.substr(pos + 1, next - pos - 1))) {
elem.join = join;
elem.level = level;
res.push(elem);
}
pos = next;
}
}
}
else if (chr == ')')
level--;
pos++;
}
return res;
};
// LDAP filter-entry parser
this.parse_ldap_filter_entry = function(entry)
{
var type = 'exact', name, value;
if (entry.match(/^([a-zA-Z0-9_-]+)=(.*)$/)) {
name = RegExp.$1;
value = RegExp.$2;
if (value == '*') {
value = ''
type = 'any';
}
else if (value.match(/^\*(.+)\*$/)) {
value = RegExp.$1;
type = 'both';
}
else if (value.match(/^\*(.+)$/)) {
value = RegExp.$1;
type = 'suffix';
}
else if (value.match(/^(.+)\*$/)) {
value = RegExp.$1;
type = 'prefix';
}
return {name: name, type: type, value: value};
}
};
// Builds LDAP filter string from the defined structure
this.build_ldap_filter = function(filter)
{
var i, elem, str = '', last = -1, join = {'AND': '&', 'OR': '|'};
for (i=0; i<filter.length; i++) {
elem = filter[i];
if (elem.level > last)
str += (elem.join && filter.length > 1 ? join[elem.join] : '');
else if (elem.level < last)
str += ')';
str += '(' + elem.name + '=';
if (elem.type == 'any')
str += '*';
else if (elem.type == 'both')
str += '*' + elem.value + '*';
else if (elem.type == 'prefix')
str += elem.value + '*';
else if (elem.type == 'suffix')
str += '*' + elem.value;
else
str += elem.value;
str += ')';
last = elem.level;
}
if (filter.length > 1)
str = '(' + str + ')';
return str;
};
// Builds LDAP URL string from the defined structure
this.build_ldap_url = function(params)
{
var url = '';
if (!params.filter.length && !params.base)
return url;
url += (params.scheme ? params.scheme : 'ldap') + '://';
url += (params.host ? params.host + (params.port && params.port != 389 ? ':'+params.port : '') : '') + '/';
url += (params.base ? params.base : '') + '?';
url += (params.attrs ? params.attrs : '') + '?';
url += (params.scope ? params.scope : '') + '?';
if (params.filter)
url += this.build_ldap_filter(params.filter);
return url;
};
};
// Add escape() method to RegExp object
// http://dev.rubyonrails.org/changeset/7271
RegExp.escape = function(str)
{
return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
};
// make a string URL safe (and compatible with PHP's rawurlencode())
function urlencode(str)
{
if (window.encodeURIComponent)
return encodeURIComponent(str).replace('*', '%2A');
return escape(str)
.replace('+', '%2B')
.replace('*', '%2A')
.replace('/', '%2F')
.replace('@', '%40');
};
// Initialize application object (don't change var name!)
var kadm = new kolab_admin();
// general click handler
$(document).click(function() {
// destroy autocompletion
kadm.ac_stop();
$('.popup').hide();
});

File Metadata

Mime Type
text/x-diff
Expires
Sat, Apr 4, 7:12 AM (1 w, 2 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18820994
Default Alt Text
(118 KB)

Event Timeline