diff --git a/lib/kolab_client_task.php b/lib/kolab_client_task.php index e8c121d..3c6402f 100644 --- a/lib/kolab_client_task.php +++ b/lib/kolab_client_task.php @@ -1,1791 +1,1791 @@ | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | | Author: Jeroen van Meeuwen | +--------------------------------------------------------------------------+ */ 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', 'ou', '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', $language . 'UTF-8', 'en_US.utf8', 'en_US.UTF-8'); // workaround for http://bugs.php.net/bug.php?id=18556 if (PHP_VERSION_ID < 50500 && in_array($language, array('tr_TR', 'ku', 'az_AZ'))) { setlocale(LC_CTYPE, 'en_US.utf8', 'en_US.UTF-8'); } 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', true); if ($login['username']) { $login['username'] = trim($login['username']); $login['domain'] = trim($login['domain']); $result = $this->api->login($login['username'], $login['password'], $login['domain'], true); 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 = $result->get('info'); if (empty($res)) { $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 might be 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; } return kolab_utils::get_input($name, $type, $allow_html); } /** * 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, $this->object_types)) { 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('
  • ' .'%s
  • ', $class, $idx, $action, $this->translate($label)); } return ''; } /** * 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', true); $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'] = '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; case 'aci': $result['type'] = kolab_form::INPUT_TEXTAREA; $result['data-type'] = 'aci'; $this->output->add_translation('aci.new', 'aci.edit', 'aci.remove', 'aci.users', 'aci.rights', 'aci.targets', 'aci.aciname', 'aci.read', 'aci.compare', 'aci.search', 'aci.write', 'aci.selfwrite', 'aci.delete', 'aci.add', 'aci.proxy', 'aci.all', 'aci.allow', 'aci.deny', 'aci.typeusers', 'aci.typegroups', 'aci.typeroles', 'aci.typeadmins', 'aci.typespecials', 'aci.ldap-all', 'aci.ldap-anyone', 'aci.ldap-self', 'aci.ldap-parent', 'aci.usersearch', 'aci.usersearchresult', 'aci.selected', 'aci.other', 'aci.userselected', 'aci.useradd', 'aci.userremove', 'aci.thisentry', 'aci.rights.target', 'aci.rights.filter', 'aci.rights.attrs', 'aci.checkall', 'aci.checknone', 'aci.error.noname', 'aci.error.exists', 'aci.error.nousers', 'button.cancel', 'button.ok' ); break; case 'imap_acl': $result['type'] = kolab_form::INPUT_TEXTAREA; $result['data-type'] = 'acl'; $result['default'] = $field['default']; $this->output->add_translation('aci.new', 'aci.edit', 'aci.remove', 'button.ok', 'button.cancel', 'acl.all', 'acl.custom', 'acl.read-only', 'acl.read-write', 'acl.full', 'acl.semi-full', 'acl.l', 'acl.r', 'acl.s', 'acl.w', 'acl.i', 'acl.p', 'acl.k', 'acl.a', 'acl.x', 'acl.t', 'acl.n', 'acl.e', 'acl.d', 'acl.anyone', 'acl.anonymous', 'acl.identifier', 'acl.rights', 'acl.expire', 'acl.user', 'acl.error.invaliddate', 'acl.error.nouser', 'acl.error.subjectexists', 'acl.error.norights' ); break; case 'text-separated': $result['type'] = kolab_form::INPUT_TEXTAREA; $result['data-type'] = 'separated'; break; default: if (!empty($field['autocomplete'])) { $result['type'] = kolab_form::INPUT_TEXTAREA; $result['data-type'] = 'list'; $result['data-maxcount'] = 1; $result['data-autocomplete'] = true; } else { $result['type'] = kolab_form::INPUT_TEXT; } 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' + 'ldap.filter_exact', 'ldap.filter_notexact' ); } } else { $result['default'] = $field['default']; } if (isset($field['maxlength'])) { $result['maxlength'] = $field['maxlength']; } } $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 = empty($data[$field['name']]) ? $resp['default'] : $data[$field['name']]; $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); } } } if (!empty($default)) { foreach ($options as $key => $value) { if (strtolower($default) == $key) { $default = strtolower($default); } } } 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 * @param string $used_for Object types filter * @param string $id_section Name of section for type_id field * * @return array Fields list, Object types list, Current type ID */ protected function form_prepare($name, &$data, $extra_fields = array(), $used_for = null, $id_section = 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 { // find default object type foreach ($types as $type_id => $type) { if ($type['is_default']) { $default = $type_id; break; } } reset($types); $data['type_id'] = $type = ($default !== null ? $default : 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) { if (!is_array($field)) { 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 event_fields list if (!empty($field['data'])) { foreach ($field['data'] as $fd) { $event_fields[$fd][] = $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)"; } } // Re-parse auto_fields again, to get attributes for auto-generation // Need to do this after form_fields have been initialized (#2980) foreach ($auto_fields as $idx => $field) { // build auto_attribs and event_fields lists if (!empty($field['data'])) { $is_data = 0; foreach ($field['data'] as $fd) { if (!isset($data[$fd]) && isset($fields[$fd]['value'])) { $data[$fd] = $fields[$fd]['value']; } if (isset($data[$fd])) { $is_data++; } } if (count($field['data']) == $is_data) { $auto_attribs[] = $idx; } } else { $auto_attribs[] = $idx; // Unset the $auto_fields array key to prevent the form field from // becoming disabled/readonly unset($auto_fields[$idx]); } } // 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) { $readonly = null; 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'] = $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' else if (!in_array('write', $attribute_rights[$idx])) { $fields[$idx]['readonly'] = $readonly = true; } } // disable auto-fields updates, user has no rights to modify them anyway if (is_bool($readonly) && $readonly) { if (($s_idx = array_search($idx, $auto_attribs)) !== false) { unset($auto_attribs[$s_idx]); } unset($auto_fields[$idx]); } } // 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 = '
    ' . $debug . '
    '; $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]; } // 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; } } // Add entry identifier if (!$add_mode) { $fields['id'] = array( 'section' => 'system', 'type' => kolab_form::INPUT_HIDDEN, 'value' => $data['id'] ); } // Add object type id selector if ($id_section) { $object_types = array(); foreach ($types as $idx => $elem) { $object_types[$idx] = array('value' => $idx, 'content' => $elem['name']); } // Add object type id selector $fields['type_id'] = array( 'section' => $id_section, 'type' => kolab_form::INPUT_SELECT, 'options' => $object_types, 'onchange' => "kadm.{$name}_save(true, '$id_section')", ); // Hide account type selector if there's only one type if (count($object_types) < 2 || !$add_mode) { $fields['type_id']['type'] = kolab_form::INPUT_HIDDEN; } // Add object type name if (!$add_mode && count($object_types) > 1) { $fields['type_id_name'] = array( 'label' => "$name.type_id", 'section' => $id_section, 'value' => $object_types[$type]['content'], ); } } $result = array($fields, $types, $type, $add_mode); 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()) * @param bool $add_mode Add mode enabled * @param string $title Page title * * @return kolab_form HTML Form object */ protected function form_create($name, $attribs, $sections, $fields, $fields_map, $data, $add_mode, $title = null) { // Assign sections to fields foreach ($fields as $idx => $field) { if (!$field['section']) { $fields[$idx]['section'] = isset($fields_map[$idx]) ? $fields_map[$idx] : 'other'; } } // 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); } $form = new kolab_form($attribs); $default_values = array(); $assoc_fields = array(); $req_fields = array(); $writeable = 0; $auto_fields = $this->output->get_env('auto_fields'); $conf_aliases = array('mailquota' => 'quota'); $domain = $this->domain ?: $_SESSION['user']['domain']; // 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']) && $data[$idx] !== null && $data[$idx] !== '') { $value = $data[$idx]; // Convert data for the list field with autocompletion if ($field['data-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'])) { // read default from config, e.g. default_quota if (!isset($field['default'])) { $conf_name = 'default_' . ($conf_aliases[$idx] ?: $idx); $field['default'] = $this->config->get($domain, $conf_name); } if (isset($field['default'])) { $field['value'] = $field['default']; $default_values[$idx] = $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 && !isset($field['checked'])) { $field['checked'] = $field['value'] == 'TRUE'; $field['value'] = 'TRUE'; } /* if (!empty($field['suffix'])) { $field['suffix'] = kolab_html::escape($this->translate($field['suffix'])); } */ if (!empty($field['options']) && empty($field['escaped'])) { 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)); } } $field['escaped'] = true; } 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; } } } $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.command('{$name}.save')", 'class' => 'submit', )); } // add Delete button if ($this->is_deletable($data)) { $id = $data['id']; $form->add_button(array( 'value' => kolab_html::escape($this->translate('button.delete')), 'onclick' => "kadm.command('{$name}.delete', '{$id}')", )); } // add Clone button if (!empty($attribs['clone-button'])) { $id = $data['id']; $form->add_button(array( 'value' => kolab_html::escape($this->translate('button.clone')), 'onclick' => "kadm.command('{$attribs['clone-button']}', '{$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('default_values', $default_values); $this->output->set_env('assoc_fields', $assoc_fields); $this->output->set_env('required_fields', $req_fields); $this->output->set_env('autocomplete_min_length', $ac_min_len); $this->output->set_env('entrydn', $data['entrydn']); $this->output->add_translation('form.required.empty', 'form.maxcount.exceeded', $name . '.add.success', $name . '.edit.success', $name . '.delete.success', $name . '.delete.confirm', $name . '.delete.force', 'add', 'edit', 'delete'); if (empty($title)) { $title = $add_mode ? $this->translate("$name.add") : $data['cn']; } $form->set_title(kolab_html::escape($title)); return $form; } /** * Check wether specified object can be deleted */ protected function is_deletable($data) { return !empty($data['id']) && in_array('delete', (array) $data['effective_rights']['entry']); } /** * 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(); // Support attribute=value searches if ($task != 'settings' && preg_match('/^([a-z0-9]+)([~<>]*=)(.+)$/i', $search, $m)) { $search_attr = $m[1]; $search_type = $m[2]; $search_value = $m[3]; if ($search_value === '*') { $search_type = 'exists'; $search_value = ''; } else if ($search_value[0] === '*' && $search_value[strlen($search_value)-1] === '*') { $search_type = 'both'; $search_value = substr($search_value, 1, -1); } else if ($search_value[0] === '*') { $search_type = 'suffix'; $search_value = substr($search_value, 1); } else if ($search_value[strlen($search_value)-1] === '*') { $search_type = 'prefix'; $search_value = substr($search_value, 0, -1); } $search_request[$search_attr] = array( 'value' => $search_value, 'type' => $search_type, ); } else { 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_class = 'list'; // 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)) { if (method_exists($this, 'list_result_handler')) { list($rows, $head, $foot, $table_class) = $this->list_result_handler($result, $head, $foot, $table_class); } else { foreach ($result as $idx => $item) { if (!is_array($item)) { continue; } $class = array('selectable'); if (method_exists($this, 'list_item_handler')) { $item = $this->list_item_handler($item, $class); } 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', '" . kolab_utils::js_escape($idx) . "')"); $rows[] = array('id' => $i, 'class' => implode(' ', $class), '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' => $table_class, '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/lib/locale/en_US.php b/lib/locale/en_US.php index 967141b..4cbe353 100644 --- a/lib/locale/en_US.php +++ b/lib/locale/en_US.php @@ -1,449 +1,450 @@ Kolab Server.'; $LANG['about.kolab'] = 'Kolab'; $LANG['about.kolabsys'] = 'Kolab Systems'; $LANG['about.support'] = 'Professional support is available from Kolab Systems.'; $LANG['about.technology'] = 'Technology'; $LANG['about.warranty'] = 'It comes with absolutely no warranties and is typically run entirely self supported. You can find help & information on the community web site & wiki.'; $LANG['aci.new'] = 'New...'; $LANG['aci.edit'] = 'Edit...'; $LANG['aci.remove'] = 'Remove'; $LANG['aci.users'] = 'Users'; $LANG['aci.rights'] = 'Rights'; $LANG['aci.targets'] = 'Targets'; $LANG['aci.aciname'] = 'ACI name:'; $LANG['aci.hosts'] = 'Hosts'; $LANG['aci.times'] = 'Times'; $LANG['aci.name'] = 'Name'; $LANG['aci.userid'] = 'User ID'; $LANG['aci.email'] = 'E-mail'; $LANG['aci.read'] = 'Read'; $LANG['aci.compare'] = 'Compare'; $LANG['aci.search'] = 'Search'; $LANG['aci.write'] = 'Write'; $LANG['aci.selfwrite'] = 'Self-write'; $LANG['aci.delete'] = 'Delete'; $LANG['aci.add'] = 'Add'; $LANG['aci.proxy'] = 'Proxy'; $LANG['aci.all'] = 'All rights'; $LANG['aci.allow'] = 'Allow'; $LANG['aci.deny'] = 'Deny'; $LANG['aci.typeusers'] = 'Users'; $LANG['aci.typegroups'] = 'Groups'; $LANG['aci.typeroles'] = 'Roles'; $LANG['aci.typeadmins'] = 'Administrators'; $LANG['aci.typespecials'] = 'Special Rights'; $LANG['aci.ldap-self'] = 'Self'; $LANG['aci.ldap-anyone'] = 'All users'; $LANG['aci.ldap-all'] = 'All authenticated users'; $LANG['aci.ldap-parent'] = 'Parent'; $LANG['aci.usersearch'] = 'Search for:'; $LANG['aci.usersearchresult'] = 'Search results:'; $LANG['aci.userselected'] = 'Selected users/groups/roles:'; $LANG['aci.useradd'] = 'Add'; $LANG['aci.userremove'] = 'Remove'; $LANG['aci.error.noname'] = 'ACI rule name is required!'; $LANG['aci.error.exists'] = 'ACI rule with specified name already exists!'; $LANG['aci.error.nousers'] = 'At least one user entry is required!'; $LANG['aci.rights.target'] = 'Target entry:'; $LANG['aci.rights.filter'] = 'Filter:'; $LANG['aci.rights.attrs'] = 'Attributes:'; $LANG['aci.checkall'] = 'Check all'; $LANG['aci.checknone'] = 'Check none'; $LANG['aci.thisentry'] = 'This entry'; $LANG['aci.selected'] = 'all selected'; $LANG['aci.other'] = 'all except selected'; $LANG['acl.all'] = 'All'; $LANG['acl.custom'] = 'Custom...'; $LANG['acl.full'] = 'Full (without access control)'; $LANG['acl.read-only'] = 'Read-Only'; $LANG['acl.read-write'] = 'Read/Write'; $LANG['acl.semi-full'] = 'Write new items'; $LANG['acl.l'] = 'l - Lookup'; $LANG['acl.r'] = 'r - Read messages'; $LANG['acl.s'] = 's - Keep Seen state'; $LANG['acl.w'] = 'w - Write flags'; $LANG['acl.i'] = 'i - Insert (Copy into)'; $LANG['acl.p'] = 'p - Post'; $LANG['acl.c'] = 'c - Create subfolders'; $LANG['acl.k'] = 'k - Create subfolders'; $LANG['acl.d'] = 'd - Delete messages'; $LANG['acl.t'] = 't - Delete messages'; $LANG['acl.e'] = 'e - Expunge'; $LANG['acl.x'] = 'x - Delete folder'; $LANG['acl.a'] = 'a - Administer'; $LANG['acl.n'] = 'n - Annotate messages'; $LANG['acl.identifier'] = 'Identifier'; $LANG['acl.rights'] = 'Access Rights'; $LANG['acl.expire'] = 'Expires On'; $LANG['acl.user'] = 'User...'; $LANG['acl.anyone'] = 'All users (anyone)'; $LANG['acl.anonymous'] = 'Guests (anonymous)'; $LANG['acl.error.invaliddate'] = 'Invalid date format!'; $LANG['acl.error.norights'] = 'No access rights specified!'; $LANG['acl.error.subjectexists'] = 'Access rights for specified identifier already exist!'; $LANG['acl.error.nouser'] = 'User identifier not specified!'; $LANG['add'] = 'Add'; $LANG['api.notypeid'] = 'No object type ID specified!'; $LANG['api.invalidtypeid'] = 'Invalid object type ID!'; $LANG['attribute.add'] = 'Add attribute'; $LANG['attribute.default'] = 'Default value'; $LANG['attribute.static'] = 'Static value'; $LANG['attribute.name'] = 'Attribute'; $LANG['attribute.optional'] = 'Optional'; $LANG['attribute.maxcount'] = 'Max. count'; $LANG['attribute.readonly'] = 'Read-only'; $LANG['attribute.type'] = 'Field type'; $LANG['attribute.value'] = 'Value'; $LANG['attribute.value.auto'] = 'Generated'; $LANG['attribute.value.auto-readonly'] = 'Generated (read-only)'; $LANG['attribute.value.normal'] = 'Normal'; $LANG['attribute.value.static'] = 'Static'; $LANG['attribute.options'] = 'Options'; $LANG['attribute.key.invalid'] = 'Type key contains forbidden characters!'; $LANG['attribute.required.error'] = 'Required attributes missing in attributes list ($1)!'; $LANG['attribute.validate'] = ' Validation'; $LANG['attribute.validate.default'] = 'default'; $LANG['attribute.validate.none'] = 'none'; $LANG['attribute.validate.basic'] = 'basic'; $LANG['attribute.validate.extended'] = 'extended'; $LANG['button.cancel'] = 'Cancel'; $LANG['button.clone'] = 'Clone'; $LANG['button.delete'] = 'Delete'; $LANG['button.ok'] = 'OK'; $LANG['button.save'] = 'Save'; $LANG['button.submit'] = 'Submit'; $LANG['creatorsname'] = 'Created by'; $LANG['days'] = 'days'; $LANG['debug'] = 'Debug info'; $LANG['delete'] = 'Delete'; $LANG['deleting'] = 'Deleting data...'; $LANG['domain.add'] = 'Add Domain'; $LANG['domain.add.success'] = 'Domain created successfully.'; $LANG['domain.associateddomain'] = 'Domain name(s)'; $LANG['domain.delete.confirm'] = 'Are you sure, you want to delete this domain?'; $LANG['domain.delete.force'] = "There are users assigned to this domain.\nAre you sure, you want to delete this domain and all assigned objects?"; $LANG['domain.delete.success'] = 'Domain deleted successfully.'; $LANG['domain.edit'] = 'Edit domain'; $LANG['domain.edit.success'] = 'Domain updated successfully.'; $LANG['domain.inetdomainbasedn'] = 'Custom Root DN'; $LANG['domain.inetdomainstatus'] = 'Status'; $LANG['domain.list'] = 'Domains List'; $LANG['domain.norecords'] = 'No domain records found!'; $LANG['domain.o'] = 'Organization'; $LANG['domain.other'] = 'Other'; $LANG['domain.system'] = 'System'; $LANG['domain.type_id'] = 'Standard Domain'; $LANG['edit'] = 'Edit'; $LANG['error'] = 'Error'; $LANG['error.401'] = 'Unauthorized.'; $LANG['error.403'] = 'Access forbidden.'; $LANG['error.404'] = 'Object not found.'; $LANG['error.408'] = 'Request timeout.'; $LANG['error.450'] = 'Domain is not empty.'; $LANG['error.500'] = 'Internal server error.'; $LANG['error.503'] = 'Service unavailable. Try again later.'; $LANG['form.required.empty'] = 'Some of the required fields are empty!'; $LANG['form.maxcount.exceeded'] = 'Maximum count of items exceeded!'; $LANG['group.add'] = 'Add Group'; $LANG['group.add.success'] = 'Group created successfully.'; $LANG['group.cn'] = 'Common name'; $LANG['group.delete.confirm'] = 'Are you sure, you want to delete this group?'; $LANG['group.delete.success'] = 'Group deleted successfully.'; $LANG['group.edit.success'] = 'Group updated successfully.'; $LANG['group.gidnumber'] = 'Primary group number'; $LANG['group.kolaballowsmtprecipient'] = 'Recipient(s) Access List'; $LANG['group.kolaballowsmtpsender'] = 'Sender Access List'; $LANG['group.list'] = 'Groups List'; $LANG['group.mail'] = 'Primary Email Address'; $LANG['group.member'] = 'Member(s)'; $LANG['group.memberurl'] = 'Members URL'; $LANG['group.norecords'] = 'No group records found!'; $LANG['group.other'] = 'Other'; $LANG['group.ou'] = 'Organizational Unit'; $LANG['group.system'] = 'System'; $LANG['group.type_id'] = 'Group type'; $LANG['group.uniquemember'] = 'Members'; $LANG['info'] = 'Information'; $LANG['internalerror'] = 'Internal system error!'; $LANG['ldap.one'] = 'one: all entries one level under the base DN'; $LANG['ldap.sub'] = 'sub: whole subtree starting with the base DN'; $LANG['ldap.base'] = 'base: base DN only'; $LANG['ldap.basedn'] = 'Base DN'; $LANG['ldap.host'] = 'LDAP Server'; $LANG['ldap.conditions'] = 'Conditions'; $LANG['ldap.scope'] = 'Scope'; $LANG['ldap.filter_any'] = 'is non-empty'; $LANG['ldap.filter_both'] = 'contains'; $LANG['ldap.filter_prefix'] = 'starts with'; $LANG['ldap.filter_suffix'] = 'ends with'; $LANG['ldap.filter_exact'] = 'is equal to'; +$LANG['ldap.filter_notexact'] = 'is not equal to'; $LANG['list.records'] = '$1 to $2 of $3'; $LANG['loading'] = 'Loading...'; $LANG['logout'] = 'Logout'; $LANG['login.username'] = 'Username:'; $LANG['login.password'] = 'Password:'; $LANG['login.domain'] = 'Domain:'; $LANG['login.login'] = 'Login'; $LANG['loginerror'] = 'Incorrect username or password!'; $LANG['MB'] = 'MB'; $LANG['menu.about'] = 'About'; $LANG['menu.domains'] = 'Domains'; $LANG['menu.groups'] = 'Groups'; $LANG['menu.ous'] = 'Units'; $LANG['menu.resources'] = 'Resources'; $LANG['menu.roles'] = 'Roles'; $LANG['menu.settings'] = 'Settings'; $LANG['menu.sharedfolders'] = 'Shared Folders'; $LANG['menu.users'] = 'Users'; $LANG['modifiersname'] = 'Modified by'; $LANG['password.generate'] = 'Generate password'; $LANG['reqtime'] = 'Request time: $1 sec.'; $LANG['ou.aci'] = 'Access Rights'; $LANG['ou.add'] = 'Add Unit'; $LANG['ou.add.success'] = 'Unit created successfully.'; $LANG['ou.ou'] = 'Unit Name'; $LANG['ou.delete.confirm'] = 'Are you sure, you want to delete this organizational unit?'; $LANG['ou.delete.success'] = 'Unit deleted successfully.'; $LANG['ou.description'] = 'Unit Description'; $LANG['ou.edit.success'] = 'Unit updated successfully.'; $LANG['ou.list'] = 'Organizational Unit List'; $LANG['ou.norecords'] = 'No organizational unit records found!'; $LANG['ou.system'] = 'Details'; $LANG['ou.type_id'] = 'Unit Type'; $LANG['ou.base_dn'] = 'Parent Unit'; $LANG['resource.acl'] = 'Access Rights'; $LANG['resource.add'] = 'Add Resource'; $LANG['resource.add.success'] = 'Resource created successfully.'; $LANG['resource.cn'] = 'Name'; $LANG['resource.delete'] = 'Delete Resource'; $LANG['resource.delete.confirm'] = 'Are you sure, you want to delete this resource?'; $LANG['resource.delete.success'] = 'Resource deleted successfully.'; $LANG['resource.edit'] = 'Edit Resource'; $LANG['resource.edit.success'] = 'Resource updated successfully.'; $LANG['resource.kolabtargetfolder'] = 'Target Folder'; $LANG['resource.kolabinvitationpolicy'] = 'Invitation Policy'; $LANG['resource.kolabdescattribute'] = 'Attributes'; $LANG['resource.list'] = 'Resource (Collection) List'; $LANG['resource.mail'] = 'Mail Address'; $LANG['resource.member'] = 'Collection Members'; $LANG['resource.norecords'] = 'No resource records found!'; $LANG['resource.other'] = 'Other'; $LANG['resource.ou'] = 'Organizational Unit'; $LANG['resource.system'] = 'System'; $LANG['resource.type_id'] = 'Resource Type'; $LANG['resource.uniquemember'] = 'Collection Members'; $LANG['resource.description'] = 'Description'; $LANG['resource.owner'] = 'Owner'; $LANG['role.add'] = 'Add Role'; $LANG['role.add.success'] = 'Role created successfully.'; $LANG['role.cn'] = 'Role Name'; $LANG['role.delete.confirm'] = 'Are you sure, you want to delete this role?'; $LANG['role.delete.success'] = 'Role deleted successfully.'; $LANG['role.description'] = 'Role Description'; $LANG['role.edit.success'] = 'Role updated successfully.'; $LANG['role.list'] = 'Role List'; $LANG['role.norecords'] = 'No role records found!'; $LANG['role.system'] = 'Details'; $LANG['role.type_id'] = 'Role Type'; $LANG['saving'] = 'Saving data...'; $LANG['search'] = 'Search'; $LANG['search.reset'] = 'Reset'; $LANG['search.criteria'] = 'Search criteria'; $LANG['search.field'] = 'Field:'; $LANG['search.method'] = 'Method:'; $LANG['search.contains'] = 'contains'; $LANG['search.is'] = 'is'; $LANG['search.key'] = 'key'; $LANG['search.prefix'] = 'begins with'; $LANG['search.name'] = 'name'; $LANG['search.email'] = 'email'; $LANG['search.description'] = 'description'; $LANG['search.uid'] = 'UID'; $LANG['search.loading'] = 'Searching...'; $LANG['search.acchars'] = 'At least $min characters required for autocompletion'; $LANG['servererror'] = 'Server Error!'; $LANG['session.expired'] = 'Session has expired. Login again, please'; $LANG['sharedfolder.acl'] = 'IMAP Access Rights'; $LANG['sharedfolder.add'] = 'Add Shared Folder'; $LANG['sharedfolder.add.success'] = 'Shared folder created successfully.'; $LANG['sharedfolder.alias'] = 'Secondary Email Address(es)'; $LANG['sharedfolder.cn'] = 'Folder Name'; $LANG['sharedfolder.delete.confirm'] = 'Are you sure, you want to delete this shared folder?'; $LANG['sharedfolder.delete.success'] = 'Shared folder deleted successfully.'; $LANG['sharedfolder.edit'] = 'Edit Shared Folder'; $LANG['sharedfolder.edit.success'] = 'Shared folder updated successfully.'; $LANG['sharedfolder.kolaballowsmtprecipient'] = 'Recipient(s) Access List'; $LANG['sharedfolder.kolaballowsmtpsender'] = 'Sender Access List'; $LANG['sharedfolder.kolabdelegate'] = 'Delegate(s)'; $LANG['sharedfolder.kolabtargetfolder'] = 'Target IMAP Folder'; $LANG['sharedfolder.list'] = 'Shared Folders List'; $LANG['sharedfolder.norecords'] = 'No shared folder records found!'; $LANG['sharedfolder.mail'] = 'Email Address'; $LANG['sharedfolder.other'] = 'Other'; $LANG['sharedfolder.system'] = 'System'; $LANG['sharedfolder.type_id'] = 'Shared Folder Type'; $LANG['signup.headline'] = 'Sign Up for Hosted Kolab'; $LANG['signup.intro1'] = 'Having an account on a Kolab server is way better than just simple Email. It also provides you with full groupware functionality including synchronization for shared addressbooks, calendars, tasks, journal and more.'; $LANG['signup.intro2'] = 'You can sign up here now for an account.'; $LANG['signup.formtitle'] = 'Sign Up'; $LANG['signup.username'] = 'Username'; $LANG['signup.domain'] = 'Domain'; $LANG['signup.mailalternateaddress'] = 'Current Email Address'; $LANG['signup.futuremail'] = 'Future Email Address'; $LANG['signup.company'] = 'Company'; $LANG['signup.captcha'] = 'CAPTCHA'; $LANG['signup.userexists'] = 'User already exists!'; $LANG['signup.usercreated'] = '

    Your account has been successfully added!

    Congratulations, you now have your own Kolab account.'; $LANG['signup.wronguid'] = 'Invalid Username!'; $LANG['signup.wrongmailalternateaddress'] = 'Please provide a valid Email Address!'; $LANG['signup.footer'] = 'This is a service offered by Kolab Systems.'; $LANG['type.add'] = 'Add Object Type'; $LANG['type.add.success'] = 'Object type created successfully.'; $LANG['type.attributes'] = 'Attributes'; $LANG['type.description'] = 'Description'; $LANG['type.delete.confirm'] = 'Are you sure, you want to delete this object type?'; $LANG['type.delete.success'] = 'Object type deleted successfully.'; $LANG['type.domain'] = 'Domain'; $LANG['type.edit.success'] = 'Object type updated successfully.'; $LANG['type.group'] = 'Group'; $LANG['type.is_default'] = 'Default'; $LANG['type.list'] = 'Object Types List'; $LANG['type.key'] = 'Key'; $LANG['type.name'] = 'Name'; $LANG['type.norecords'] = 'No object type records found!'; $LANG['type.objectclass'] = 'Object class'; $LANG['type.object_type'] = 'Object type'; $LANG['type.ou'] = 'Organizational Unit'; $LANG['type.properties'] = 'Properties'; $LANG['type.resource'] = 'Resource'; $LANG['type.role'] = 'Role'; $LANG['type.sharedfolder'] = 'Shared Folder'; $LANG['type.used_for'] = 'Hosted'; $LANG['type.user'] = 'User'; $LANG['user.add'] = 'Add User'; $LANG['user.add.success'] = 'User created successfully.'; $LANG['user.alias'] = 'Secondary Email Address(es)'; $LANG['user.astaccountallowedcodec'] = 'Allowed codec(s)'; $LANG['user.astaccountcallerid'] = 'Caller ID'; $LANG['user.astaccountcontext'] = 'Account Context'; $LANG['user.astaccountdefaultuser'] = 'Asterisk Account Default User'; $LANG['user.astaccountdeny'] = 'Account deny'; $LANG['user.astaccounthost'] = 'Asterisk Host'; $LANG['user.astaccountmailbox'] = 'Mailbox'; $LANG['user.astaccountnat'] = 'Account uses NAT'; $LANG['user.astaccountname'] = 'Asterisk Account Name'; $LANG['user.astaccountqualify'] = 'Account Qualify'; $LANG['user.astaccountrealmedpassword'] = 'Realmed Account Password'; $LANG['user.astaccountregistrationexten'] = 'Extension'; $LANG['user.astaccountregistrationcontext'] = 'Registration Context'; $LANG['user.astaccountsecret'] = 'Plaintext Password'; $LANG['user.astaccounttype'] = 'Account Type'; $LANG['user.astcontext'] = 'Asterisk Context'; $LANG['user.asterisk'] = 'Asterisk SIP'; $LANG['user.astextension'] = 'Asterisk Extension'; $LANG['user.astvoicemailpassword'] = 'Voicemail PIN Code'; $LANG['user.c'] = 'Country'; $LANG['user.city'] = 'City'; $LANG['user.cn'] = 'Common name'; $LANG['user.config'] = 'Configuration'; $LANG['user.contact'] = 'Contact'; $LANG['user.contact_info'] = 'Contact Information'; $LANG['user.country'] = 'Country'; $LANG['user.country.desc'] = '2 letter code from ISO 3166-1'; $LANG['user.delete.confirm'] = 'Are you sure, you want to delete this user?'; $LANG['user.delete.success'] = 'User deleted successfully.'; $LANG['user.displayname'] = 'Display name'; $LANG['user.edit.success'] = 'User updated successfully.'; $LANG['user.fax'] = 'Fax number'; $LANG['user.fbinterval'] = 'Free-Busy interval'; $LANG['user.fbinterval.desc'] = 'Leave blank for default (60 days)'; $LANG['user.gidnumber'] = 'Primary group number'; $LANG['user.givenname'] = 'Given name'; $LANG['user.homedirectory'] = 'Home directory'; $LANG['user.homephone'] = 'Home Phone Number'; $LANG['user.initials'] = 'Initials'; $LANG['user.invitation-policy'] = 'Invitation policy'; $LANG['user.kolaballowsmtprecipient'] = 'Recipient(s) Access List'; $LANG['user.kolaballowsmtpsender'] = 'Sender Access List'; $LANG['user.kolabdelegate'] = 'Delegates'; $LANG['user.kolabhomeserver'] = 'Email Server'; $LANG['user.kolabinvitationpolicy'] = 'Invitation Handling Policy'; $LANG['user.l'] = 'City, Region'; $LANG['user.list'] = 'Users List'; $LANG['user.loginshell'] = 'Shell'; $LANG['user.mail'] = 'Primary Email Address'; $LANG['user.mailalternateaddress'] = 'External Email Address(es)'; $LANG['user.mailforwardingaddress'] = 'Forward Mail To'; $LANG['user.mailhost'] = 'Email Server'; $LANG['user.mailquota'] = 'Quota'; $LANG['user.mailquota.desc'] = 'Leave blank for unlimited'; $LANG['user.mobile'] = 'Mobile Phone Number'; $LANG['user.name'] = 'Name'; $LANG['user.norecords'] = 'No user records found!'; $LANG['user.nsrole'] = 'Role(s)'; $LANG['user.nsroledn'] = 'Role(s)'; $LANG['user.other'] = 'Other'; $LANG['user.o'] = 'Organization'; $LANG['user.org'] = 'Organization'; $LANG['user.orgunit'] = 'Organizational Unit'; $LANG['user.ou'] = 'Organizational Unit'; $LANG['user.pager'] = 'Pager Number'; $LANG['user.password.notallowed'] = 'Password contains characters (eg. Umlaut) that are not permitted'; $LANG['user.password.mismatch'] = 'Passwords do not match!'; $LANG['user.password.moreupper'] = 'Password needs to contain at least $1 uppercase character(s)'; $LANG['user.password.morelower'] = 'Password needs to contain at least $1 lowercase character(s)'; $LANG['user.password.moredigits'] = 'Password needs to contain at least $1 digit(s)'; $LANG['user.password.morespecial'] = 'Password needs to contain at least $1 special character(s): $2'; $LANG['user.password.tooshort'] = 'Password is too short, it must have at least $1 characters'; $LANG['user.personal'] = 'Personal'; $LANG['user.phone'] = 'Phone number'; $LANG['user.postalcode'] = 'Postal Code'; $LANG['user.postbox'] = 'Postal box'; $LANG['user.postcode'] = 'Postal code'; $LANG['user.preferredlanguage'] = 'Native tongue'; $LANG['user.room'] = 'Room number'; $LANG['user.sn'] = 'Surname'; $LANG['user.street'] = 'Street'; $LANG['user.system'] = 'System'; $LANG['user.telephonenumber'] = 'Phone Number'; $LANG['user.title'] = 'Job Title'; $LANG['user.type_id'] = 'Account type'; $LANG['user.uid'] = 'Unique identity (UID)'; $LANG['user.userpassword'] = 'Password'; $LANG['user.userpassword2'] = 'Confirm password'; $LANG['user.uidnumber'] = 'User ID number'; $LANG['welcome'] = 'Welcome to the Kolab Groupware Server Maintenance'; $LANG['yes'] = 'yes'; diff --git a/public_html/js/kolab_admin.js b/public_html/js/kolab_admin.js index 368557a..1701a8e 100644 --- a/public_html/js/kolab_admin.js +++ b/public_html/js/kolab_admin.js @@ -1,3639 +1,3649 @@ /* +--------------------------------------------------------------------------+ | This file is part of the Kolab Web Admin Panel | | | | Copyright (C) 2011-2014, Kolab Systems AG | | | | This program is free software: you can redistribute it and/or modify | | it under the terms of the GNU Affero General Public License as published | | 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 | +--------------------------------------------------------------------------+ | Author: Aleksander Machniak | +--------------------------------------------------------------------------+ */ function kolab_admin() { var self = this; this.env = {}; this.translations = {}; this.request_timeout = 300; this.message_time = 3000; this.events = {}; this.attr_default_rx = /^(text|select|imap_acl)/; // set jQuery ajax options $.ajaxSetup({ cache: false, error: function(request, status, err) { self.http_error(request, status, err); }, beforeSend: function(xmlhttp) { xmlhttp.setRequestHeader('X-Session-Token', self.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'); if (ret === false) this.set_busy(false); 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() { self.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; 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 = $('
    '); if (type != 'loading') { msg = '
    ' + msg + '
    '; obj.addClass(type).click(function() { return self.hide_message(); }); } if (timeout > 0) window.setTimeout(function() { self.hide_message(type, type != 'loading'); }, timeout); if (type == 'loading') this.hide_message(type); 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); } // modal dialog popup this.modal_dialog = function(content, buttons, opts) { var settings = {btns: {}}, body = $(''), head, foot, footer = []; // title bar if (opts && opts.title) $('') .append($('').text(opts.title)) .appendTo(body); // dialog content if (typeof content != 'object') content = $('
    ').html(content); content.addClass('modal_msg').appendTo(body); // buttons $.each(buttons, function(i, v) { var n = i.replace(/[^a-z0-9_]/ig, ''); settings.btns[n] = v; footer.push({name: n, label: self.t(i)}); }); // if (!settings.btns.cancel && (!opts || !opts.no_cancel)) // settings.btns.cancel = function() { this.hide(); }; if (footer.length) { foot = $(''); $.each(footer, function() { $('
    ').addClass('modal_btn_' + this.name).text(this.label).appendTo(foot); }); body.append(foot); } // configure and display dialog body.wModal(settings).wModal('show'); }; this.tree_list_init = function() { $('table.list.tree span.expando').click(function() { var tr = $(this).parents('table.list.tree tr'), expanded = tr.hasClass('expanded'), level = tr.data('level') || 0, row = tr[0], found = false; tr[expanded ? 'removeClass' : 'addClass']('expanded'); $('tr', tr.parent()).each(function() { if (this === row) { found = true; return; } if (!found) return; var r = $(this), l = r.data('level') || 0; if (l <= level) return false; if (!expanded && l == level+1) r.show(); else if (expanded && l > level) r.hide().removeClass('expanded'); }); return false; }); }; // position and display popup this.popup_show = function(e, popup) { var popup = $(popup), pos = this.mouse_pos(e), win = $(window), w = popup.width(), h = popup.height(), left = pos.left - w, top = pos.top; if (top + h > win.height()) top -= h; if (left + w > win.width()) left -= w; popup.css({left: left + 'px', top: top + 'px'}).show(); e.stopPropagation(); }; /********************************************************/ /********* Remote request methods *********/ /********************************************************/ // 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; }; /*********************************************************/ /********* keyboard autocomplete methods *********/ /*********************************************************/ this.ac_init = function(obj, props) { if (props && props.form) { if (i = $('[name="type_id"]', props.form).val()) props.type_id = i; if (i = $('[name="object_type"]', props.form).val()) props.object_type = i; if (i = $('[name="id"]', props.form).val()) props.id = i; delete props['form']; } 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(); e.stopPropagation(); return false; 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 = $('
      '); this.ac_pane = $('
      ').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, '>').replace(/##([^%]+)%%/g, '$1'); 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), aci_fields = $('textarea[data-type="aci"]', form); this.aci = {}; this.acl = {}; 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); }); // create IMAP ACL fields $('textarea[data-type="acl"]', form) .each(function() { kadm.form_acl_element_wrapper(this); }); // create ACI fields aci_fields.each(function() { kadm.form_aci_element_wrapper(this); }); if (aci_fields.length) this.form_aci_init(); }; // Form serialization 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); }); // IMAP ACL fields $('textarea[data-type="acl"]:not(:disabled):not([readonly])', form).each(function() { data.json = kadm.form_acl_element_submit(this.name, data.json, form); }); // ACI fields $('textarea[data-type="aci"]:not(:disabled):not([readonly])', form).each(function() { data.json = kadm.form_aci_element_submit(this.name, data.json, form); }); // text-separated fields (convert text into array) $('textarea[data-type="separated"]:not(:disabled)', form).each(function() { data.json[this.name] = data.json[this.name] ? data.json[this.name].split(/[\r\n]+/) : ''; }); // 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']; }); // "boolean" checkbox inputs $('input[type="checkbox"][value="TRUE"]', 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'), maxcount = e.attr('data-maxcount'), area = $(''); 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 = $(''); elem.attr({ value: v, disabled: disabled, readonly: readonly, name: form_element.name + '[' + (j++) + ']' }) elem = $('').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 this.ac_init(elem, { form: form, attribute: form_element.name, oninsert: this.form_element_oninsert }); // when max=1 we use only one input if (maxcount == 1) { $.each(list, function(i, v) { $('input', elem).val(v); return false; }); list = []; } 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 = '' + (!ac ? '' : ac && idx == -1 ? '' : '') + (!ac || idx >= 0 ? '' : '') + ''; 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'); if (maxcount == 1) { $(input).val(val); af[name] = {}; af[name][key] = val; return; } // 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 = $(''), content = $(''), list = this.env.assoc_fields ? this.env.assoc_fields[form_element.name] : []; if (elem.length) { $('#selectarea_' + name).remove(); $('#selectcontent_' + name).remove(); } else { elem = $('') .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 = $('') .data('value', data.key).text(data.value) .click(function(e) { var val = $(this).data('value'), elem = $(data.element), old_val = elem.val(); $('span.link', elem.parent()).text(val); elem.val(val); if (val != old_val) elem.change(); }); return elem; }; // initialize ACI fields in form this.form_aci_init = function() { // get list of ldap attributes for ACI form if (!this.ldap_attributes) { this.api_post('form_value.select_options', {attributes: ['attribute']}, 'form_aci_init_response'); } }; this.form_aci_init_response = function(response) { if (!this.api_response(response)) return; this.ldap_attributes = response.result.attribute ? response.result.attribute.list : []; }; // Replaces form element with ACI element this.form_aci_element_wrapper = function(form_element) { var i, e = $(form_element), form = form_element.form, name = form_element.name, div = $('
      '), select = $(''), table = $('
      '), buttons = [ $('').attr({value: this.t('aci.new')}), $('').attr({value: this.t('aci.edit'), disabled: true}), $('').attr({value: this.t('aci.remove'), disabled: true}) ], aci = this.parse_aci(e.val()) || []; this.aci[name] = aci; e.hide(); // this.log(e.val()); // this.log(aci); $.each(aci, function(i, entry) { $('').val(i).text(entry.name).appendTo(select) .on('dblclick', function () { self.form_aci_dialog(name, this.value); }); }); select.attr('id', 'aci'+name).on('change', function() { var selected = $(this).val() || []; buttons[1].prop('disabled', selected.length != 1); buttons[2].prop('disabled', selected.length == 0); }); // click on 'new' and 'edit' button buttons[0].on('click', function() { self.form_aci_dialog(name); }); buttons[1].on('click', function() { var selected = select.val(); self.form_aci_dialog(name, selected && selected.length ? selected[0] : null); }); // click on 'remove' button buttons[2].on('click', function() { $.each(select.val() || [], function(i, v) { self.aci[name][v] = null; $('option[value="' + v + '"]', select).remove(); }); buttons[1].prop('disabled', true); buttons[2].prop('disabled', true); }); $('.buttons', table).append(buttons); $('.list', table).append(select); div.append(table) $(form_element).parent().append(div); }; // updates form data with ACI (on form submit) this.form_aci_element_submit = function(name, data, form) { data[name] = this.build_aci(this.aci[name]); return data; }; // display aci dialog this.form_aci_dialog = function(name, id) { var aci = id ? this.aci[name][id] : {}; this.aci_dialog_aci = aci; this.aci_dialog_name = name; this.aci_dialog_id = id; this.modal_dialog(this.form_aci_dialog_content(aci), this.form_aci_dialog_buttons()); window.setTimeout(function() { $('#aci-name').focus(); }, 100); }; // return aci dialog buttons this.form_aci_dialog_buttons = function() { var buttons = { 'button.ok': function() { if (self.form_aci_dialog_submit()) { this.hide(); $('#aci-dialog').remove(); } }, 'button.cancel': function() { this.hide(); $('#aci-dialog').remove(); }, }; return buttons; }; // build and return aci dialog content this.form_aci_dialog_content = function(aci) { var dialog = $('#aci-dialog'); if (!dialog.length) { var i, tabs = [ $('
      ').attr('id', 'aci-tab-users') .append($('').text(this.t('aci.users'))) .append(this.form_aci_dialog_tab_users()), $('
      ').attr('id', 'aci-tab-rights') .append($('').text(this.t('aci.rights'))) .append(this.form_aci_dialog_tab_rights()), $('
      ').attr('id', 'aci-tab-targets') .append($('').text(this.t('aci.targets'))) .append(this.form_aci_dialog_tab_targets()) ]; dialog = $('
      ' + '
      ') .hide().appendTo('body'); dialog.children('label').text(this.t('aci.aciname')); $('#aci-form').append(tabs); this.trigger_event('form-load', 'aci-form'); } // reset form elements this.form_aci_dialog_reset(aci); return dialog.show(); }; this.form_aci_dialog_reset = function(aci) { var users = $('#aci-users').html(''), rights = aci.perms ? aci.perms[0].rights : [], inputs = $('#aci-rights input').prop('checked', false), target = $('#aci-targets-target').val(''), target_filter = $('#aci-targets-filter').val(''), target_operator = $('#aci-targets input[name="attr-operator"]'), rule = aci.perms ? aci.perms[0].type : 'userdn'; $.each(rights, function(i, v) { $('#aci-rights-' + v).click(); }); $('#aci-name').val(aci.name); $('#aci-rights-type').val(aci.perms ? aci.perms[0].type : ''); $('#aci-users-button-remove').prop('disabled', true); target_operator.filter('[value="="]').prop('checked', true); target_operator.filter('[value="!="]').prop('checked', false); $.each(aci.perms ? aci.perms : [], function(i, perm) { $.each(perm.rules || [], function(n, rule) { // these permission rules we do not support here if (!/^(userdn|groupdn|roledn)$/i.test(rule.keyword) || rule.operator != '=') return; $.each(rule.expression || [], function(x, v) { if (v.substr(0, 8) == 'ldap:///') { v = v.substr(8); } var t = v; if (t == 'all' || t == 'self' || t == 'anyone' || t == 'parent') t = self.t('aci.ldap-' + t); else if (/^cn=([^,]+)/.test(t)) t = RegExp.$1; // @TODO: resolve users DN with user names $('').attr({value: rule.keyword + ':' + v}).text(t).appendTo(users); }); }); }); $.each(aci.targets || [], function(i, v) { switch (v.type) { case 'targetfilter': target_filter.val(v.expression); break; case 'targetattr': if (v.expression[0] == '*') $('#aci-targets-attr option').prop('selected', true); else $('#aci-targets-attr').val(v.expression); target_operator.filter('[value="="]').prop('checked', v.operator == '='); target_operator.filter('[value="!="]').prop('checked', v.operator == '!='); break; case 'target': target.val(v.expression); break; } }); }; // submits aci dialog, updates aci definition in form this.form_aci_dialog_submit = function() { var val, rules = [], rights = [], name_input = $('#aci-name'), name = name_input.val(), rights_type = $('#aci-rights-type').val(), aci_list = $('#aci' + this.aci_dialog_name), exists = false, aci = {perms: [], targets: [], version: '3.0', name: name}; // sanity checks if (!name) { alert(this.t('aci.error.noname')); name_input.focus(); return false; } $.each(this.aci[self.aci_dialog_name] || [], function(i, v) { if (v && v.name == name && (!self.aci_dialog_id || self.aci_dialog_id != i)) { exists = true; return false; } }); if (exists) { alert(this.t('aci.error.exists')); name_input.focus(); return false; } // permissions $('#aci-users option').each(function() { var keyword, value = this.value; /^([a-z]+):/.test(value); keyword = RegExp.$1; value = value.substr(keyword.length + 1); rules.push({ join: 'or', operator: '=', keyword: keyword, expression: ['ldap:///' + value] }); }); if (!rules.length) { alert(this.t('aci.error.nousers')); return false; } $('#aci-rights input').each(function() { if (this.checked) { if (this.value == 'all') { rights = ['all']; return false; } rights.push(this.value); } }); if (!rights.length) { rights = ['all']; rights_type = rights_type == 'allow' ? 'deny' : 'allow'; } aci.perms.push({ rights: rights, type: rights_type, rules: rules }); // targets if ((v = $('#aci-targets-attr').val() || []).length) aci.targets.push({ type: 'targetattr', expression: v.length == $('#aci-targets-attr option').length ? ['*'] : v, operator: $('#aci-targets input[name="attr-operator"][value="!="]').is(':checked') ? '!=' : '=', }); if (v = $('#aci-targets-target').val()) aci.targets.push({ type: 'target', expression: v, operator: '=' }); if (v = $('#aci-targets-filter').val()) aci.targets.push({ type: 'targetfilter', expression: v, operator: '=' // @TODO, }); // this.log(aci); // this.log(this.build_aci([aci])); if (this.aci_dialog_id) { this.aci[this.aci_dialog_name][this.aci_dialog_id] = aci; $('option[value="' + this.aci_dialog_id + '"]', aci_list).text(aci.name); } else { this.aci[this.aci_dialog_name].push(aci); $('').val(this.aci[this.aci_dialog_name].length-1) .text(aci.name) .appendTo(aci_list) .on('dblclick', function () { self.form_aci_dialog(self.aci_dialog_name, this.value); }); } return true; }; // tab Users in aci dialog this.form_aci_dialog_tab_users = function() { var select = $(''), table = $('
      '), buttons = [ $('').attr({value: this.t('aci.new')}), $('').attr({value: this.t('aci.remove'), disabled: true}) ]; select.on('change', function() { var selected = $(this).val() || []; buttons[1].attr('disabled', selected.length == 0); }); // click on 'new' button buttons[0].on('click', function() { self.form_aci_user_dialog(); }); // click on 'remove' button buttons[1].on('click', function() { $.each(select.val() || [], function(i, v) { $('option[value="' + v + '"]', select).remove(); }); $(this).prop('disabled', true); }); $('.buttons', table).append(buttons); $('.list', table).append(select); return table; }; // tab Rights in aci dialog this.form_aci_dialog_tab_rights = function() { var div = $('
      '), select = $(''), types = ['allow', 'deny'], rights = ['read', 'compare', 'search', 'write', 'selfwrite', 'delete', 'add', 'proxy', 'all'], inputs = []; $.each(rights, function(i, v) { var input = $('').attr({value: v, id: 'aci-rights-' + v}); inputs.push($('').text(self.t('aci.' + v)).prepend(input)); if (v == 'all') input.on('change', function() { var list = $('input:not(#aci-rights-all)', div); if (this.checked) list.prop({checked: true, disabled: true}); else list.prop({disabled: false}); }); }); $.each(types, function(i, v) { $('').attr({value: v}).text(self.t('aci.' + v)).appendTo(select); }); return div.append(select).append(inputs); }; // tab Targets in aci dialog this.form_aci_dialog_tab_targets = function() { var opts = [], content = $('
      '), target = $(''), filter = $(''), button = $('').val(this.t('aci.thisentry')) .on('click', function() { target.val('ldap:///' + self.env.entrydn); }), select = $(''), radio = [ $('