diff --git a/lib/kolab_client_task.php b/lib/kolab_client_task.php index 5365b51..a420e24 100644 --- a/lib/kolab_client_task.php +++ b/lib/kolab_client_task.php @@ -1,1760 +1,1761 @@ | +--------------------------------------------------------------------------+ | 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' ); } } 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(); 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/public_html/skins/default/style.css b/public_html/skins/default/style.css index 5a8c598..856ea2d 100644 --- a/public_html/skins/default/style.css +++ b/public_html/skins/default/style.css @@ -1,1320 +1,1324 @@ body { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; color: #333; margin: 0; color: #514949; background: url(images/body.png) top repeat-x #f0f0f0; } a { color: #1E90FF; text-decoration: none; } h1, h2, h3, h4, h5, h6 { margin-top: 5px; color: #ff9900; } input[type="text"], input[type="password"], select[multiple="multiple"], textarea { border: 1px solid #d0d0d0; border-radius: 3px; -moz-border-radius: 3px; -webkit-border-radius: 3px; padding-left: 2px; color: black; } select[multiple="multiple"] { padding-left: 0; } table.list { border: 1px solid #d0d0d0; border-spacing: 0; border-radius: 3px; width: 100%; -moz-border-radius: 3px; -webkit-border-radius: 3px; } table.list td { padding: 2px 4px; border: 1px solid white; border-left: none; border-top: none; } table.list thead tr { background-color: #e0e0e0; font-weight: bold; } table.list tbody tr { background-color: #f0f0f0; } table.list tfoot tr { background-color: #e0e0e0; } table.list tfoot tr td { padding: 3px 3px; font-size: 10px; text-shadow: white 1px 1px; } table.list tfoot tr td { border-top: solid 1px white; border-bottom: none; } table.list td:last-child { border-right: none; } table.list tbody tr:last-child td { border-bottom: none; } table.list tbody tr.selectable td { cursor: default; } table.list tbody tr.selectable:hover { background-color: #d6efff; } table.list tbody tr td.empty-body { height: 150px; color: #ff9900; text-align: center; } table.list tbody tr.deleted td { color: #999; } fieldset { border: 1px solid #d0d0d0; border-radius: 3px; -moz-border-radius: 3px; -webkit-border-radius: 3px; margin-top: 10px; } legend { padding-left: 0; color: #909090; } table.form { width: 100%; } table.form td { padding: 1px 5px; } table.form select { max-width: 500px; } table.form tr.required input, table.form tr.required select, table.form tr.required textarea { background-color: #f0f9ff; } table.form tr input.error, table.form tr select.error, table.form tr textarea.error { background-color: #f5e3e3; } td.label { width: 1%; min-width: 120px; text-align: right; font-weight: bold; white-space: nowrap; } table.list.tree td span.expando { background: url(images/buttons.png) -4px -161px no-repeat; width: 16px; height: 14px; cursor: pointer; } table.list.tree tr.expanded td span.expando { background-position: -4px -180px; } table.list.tree td span.expando, table.list.tree td span.spacer, table.list.tree td span.level { display: inline-block; } table.list.tree td span.spacer { width: 16px; } /**** Common UI elements ****/ #topmenu { text-align: right; height: 20px; padding: 0 10px; margin: 0; white-space: nowrap; background: url(images/linen_header.jpg) 0 0 repeat-x; } #topmenu > span { color: #aaa; font-size: 11px; padding-top: 2px; height: 20px; line-height: 20px; } #navigation { margin: 0 15px; text-align: right; height: 36px; z-index: 2; white-space: nowrap; } #task_navigation { margin: 0 15px; text-align: right; height: 21px; z-index: 2; white-space: nowrap; } #message { position: absolute; top: 80px; left: 20px; width: 350px; height: 60px; z-index: 20; } #message div { opacity: 0.97; padding-left: 70px; border: 1px solid #d0d0d0; border-radius: 3px; -moz-border-radius: 3px; -webkit-border-radius: 3px; -moz-box-shadow: 1px 1px 3px #999; -webkit-box-shadow: #999 1px 1px 3px; width: 100%; height: 100%; display: table; } #message.notice div { border-color: #aec1db; color: #3465a4; background: url(images/info.png) 10px center no-repeat #c0e0ff; } #message.error div { border-color: #db9999; color: #a40000; background: url(images/error.png) 10px center no-repeat #edcccc; } #message div span { vertical-align: middle; display: table-cell; line-height: 20px; } #logo { position: absolute; top: 0px; left: 10px; cursor: pointer; } #content { min-height: 400px; margin: 0 15px; padding: 10px; background-color: #f5f5f5; border: 1px solid #d0d0d0; border-radius: 3px; -moz-border-radius: 3px; -webkit-border-radius: 3px; } #footer { margin: 2px 15px; color: #b0b0b0; font-size: 9px; margin-bottom: 10px; clear: both; } #loading { position: absolute; display: none; top: 2px; left: 15px; width: 150px; height: 18px; padding-left: 86px; color: #aaa; font-size: 11px; z-index: 1; background: url(images/loading.gif) 0 3px no-repeat; white-space: nowrap; } #topmenu .logout { background: url(images/buttons.png) -1px -101px no-repeat; padding-left: 20px; margin-right: 10px; color: white; } #topmenu .login { padding-left: 20px; margin-right: 20px; background: url(images/user_ico.png) 0 2px no-repeat; } #topmenu .domain { padding-left: 20px; margin-right: 10px; background: url(images/domain_ico.png) 0 3px no-repeat; } #navigation ul { list-style-type: none; margin: 0; padding: 8px 0; } #navigation ul li { display: inline; font-size: 13px; font-weight: bold; padding: 8px 0; } #navigation ul li a { padding: 8px 10px; text-decoration: none; color: #514949; } #navigation ul li.active { background: url(images/navbg.png) 0 0 repeat-x; } #navigation ul li.active a { text-shadow: white 1px 1px; color: #ff9900; } #task_navigation ul { list-style-type: none; margin: 0; padding: 2px 0; } #task_navigation ul li { padding: 1px 0; display: inline; font-size: 12px; font-weight: bold; text-shadow: white 1px 1px; } #task_navigation ul li a { padding: 1px 10px; text-decoration: none; color: #808080; } #navigation ul li a:active, #task_navigation ul li a:active { outline: none; } #taskcontent { min-height: 380px; padding: 10px; background-color: #f0f0f0; border: 1px solid #d0d0d0; border-radius: 3px; -moz-border-radius: 3px; -webkit-border-radius: 3px; overflow-x: auto; } #toc { float: left; width: 260px; min-height: 380px; } #search { padding: 7px; margin-bottom: 10px; border: 1px solid #d0d0d0; border-radius: 3px; -moz-border-radius: 3px; -webkit-border-radius: 3px; background-color: #e0e0e0; } #searchinput { border: none; background-color: white; margin-top: 2px; } #searchinput:focus { outline: none; } .searchinput { height: 20px; margin: 0; padding: 0; background-color: white; border: 1px solid #d0d0d0; border-radius: 3px; -moz-border-radius: 3px; -webkit-border-radius: 3px; overflow: hidden; } .searchactions { float: left; padding: 1px; margin-left: -1px; height: 18px; width: 37px; background-color: #f0f0f0; cursor: pointer; border-right: 1px solid #e0e0e0; border-top-left-radius: 3px; border-bottom-left-radius: 3px; -moz-border-radius-topleft: 3px; -moz-border-radius-bottomleft: 3px; -webkit-border-top-left-radius: 3px; -webkit-border-left-right-radius: 3px; } #search-reset, #search-details { display: block; float: left; width: 18px; height: 18px; background: url(images/buttons.png) -1px 0 no-repeat; } #search-reset:hover, #search-details:hover { background-color: white; } #search-reset { border-left: 1px solid #e0e0e0; } #search-details { background-position: -1px -20px; } .searchdetails { display: none; } .searchfilter { color: #909090; font-weight: bold; margin-top: 5px; } #search fieldset { margin: 0; color: #909090; margin-top: 5px; } #search td.label { min-width: 0; } div.vsplitter { float: left; width: 10px; min-height: 400px; } /**** Common classes ****/ .nowrap { white-space: nowrap; } .clear { clear: both; } .watermark { padding-top: 40px; text-align: center; width: 100%; } .link { cursor: pointer; } .icon { width: 16px; height: 16px; } input.inactive { color: #d0d0d0; } .formtitle { color: #ff9900; font-size: 18px; font-weight: bold; margin-left: 5px; } .formbuttons { text-align: center; white-space: nowrap; } +.formbuttons .submit { + font-weight: bold; +} + .formbuttons input { margin: 5px; } .listnav { width: 100%; text-align: right; } .listnav a { float: left; display: block; width: 16px; height: 16px; background: url(images/arrow_left.png) center no-repeat; } .listnav a.next { background: url(images/arrow_right.png) center no-repeat; } .listnav a.disabled { opacity: .3; cursor: default; } .listnav span span { float: left; display: block; height: 14px; padding: 1px 5px; } .disabled, .readonly, .select.readonly option { color: #a0a0a0; } input.disabled, input.readonly, select.disabled, select.readonly, textarea.disabled, textarea.readonly { background-color: #f5f5f5; color: #a0a0a0; } input.maxsize { width: 368px; /* span.listarea width - 2px */ } pre.debug { border: 1px solid #d0d0d0; border-radius: 3px; -moz-border-radius: 3px; -webkit-border-radius: 3px; background-color: white; padding: 2px; width: 500px; height: 200px; margin: 0; overflow: auto; } .popup { display: none; position: absolute; border: none; border-radius: 3px; box-shadow: 0 2px 6px 0 #333; -moz-box-shadow: 0 2px 6px 0 #333; -webkit-box-shadow: 0 2px 6px 0 #333; -o-box-shadow: 0 2px 6px 0 #333; } a.button { display: inline-block; width: 18px; height: 18px; background: url(images/buttons.png) 0 0 no-repeat; } a.button.edit { background-position: -1px -81px; } a.button.add { background-position: -1px -41px; } a.button.delete { background-position: -1px -1px; } .xdsoft_datetimepicker { border-radius: 4px; } /********* Form smart inputs *********/ span.listarea { display: block; width: 370px; max-height: 209px; overflow-y: auto; overflow-x: hidden; margin: 0; padding: 0; background-color: white; border: 1px solid #d0d0d0; border-radius: 3px; -moz-border-radius: 3px; -webkit-border-radius: 3px; text-align: left; text-shadow: none; color: black; } span.listelement { display: block; padding: 0; margin: 0; height: 18px; line-height: 18px; overflow: hidden; border-top: 1px solid #d0d0d0; white-space: nowrap; } span.listelement:first-child { border: none; } span.listelement input { border: none; background-color: transparent; padding-left: 2px; width: 328px; height: 17px; line-height: 17px; } span.listarea.disabled span.listelement input, span.listarea.readonly span.listelement input { width: 370px; } span.listelement input:focus { outline: none; } span.listelement span.actions { float: left; padding: 0; margin-left: -1px; margin-top: -1px; height: 18px; width: 37px; border: 1px solid #d0d0d0; background-color: #f0f0f0; cursor: pointer; } span.listelement span.actions span { display: block; float: left; width: 18px; height: 18px; background: url(images/buttons.png) 0 0 no-repeat; } span.listelement span.actions span:hover { background-color: white; } span.listelement span.actions span.reset { background-position: -1px 0; border-left: 1px solid #e0e0e0; } span.listelement span.actions span.add { background-position: -1px -41px; } span.listelement span.actions span.search { background-position: -1px -60px; cursor: default; } span.listarea.disabled, span.listarea.readonly { background-color: #f5f5f5; } input.disabled, input.readonly, span.listarea.disabled span.listelement input, span.listarea.readonly span.listelement input { color: #a0a0a0; cursor: default; } span.listarea.autocomplete span.listelement input { color: #514949; } span.listarea.autocomplete span.listelement input.autocomplete { color: black; } .autocomplete > span.listelement input { width: 348px; border-radius: 0; } .autocomplete > span.listelement span.actions { width: 18px; } .autocomplete > span.listelement span.actions span.reset { border-left: none; } .autocomplete > span.listelement span.actions span.search:hover { background-color: #f0f0f0; } span.listarea.select { width: 200px; } span.listarea.select > span.listelement input { width: 180px; } span.listcontent { display: block; padding: 0; margin: 0; overflow: hidden; max-height: 94px; overflow-x: hidden; overflow-y: auto; border-top: 1px solid #d0d0d0; background-color: #f5f5f5; cursor: default; } span.listcontent span.listelement { padding-left: 3px; } span.listcontent span.listelement:hover { background-color: #d6efff; } span.listcontent span.listelement.selected { background-color: #d6efff; } span.form_error { color: #FF0000; font-weight: bold; font-size: 90%; padding-left: 5px; } .ldap_url, .aci { background-color: #F5F5F5; border: 1px solid #D0D0D0; border-radius: 3px 3px 3px 3px; } .ldap_url td, li { white-space: nowrap; } .ldap_url ul { list-style-type: none; padding: 0; margin: 0; } .ldap_url ul li { padding: 0; margin: 0; } .ldap_url ul li input { width: 120px; margin-right: 5px; } .ldap_url tr span { padding: 0 5px; } .ldap_url tr.ldap_base td input, .ldap_url tr.ldap_scope td select { width: 368px; } .ldap_url tr.ldap_host td input.ldap_host { width: 290px; } table.form tr.required .ldap_url input, table.form tr.required .ldap_url select { background-color: white; } table.form tr.required .ldap_url { background-color: #F0F9FF; } .ldap_url a.button { vertical-align: middle; height: 22px; } .aci { padding: 5px; } .acltable select { width: 400px; } .acltable .buttons { vertical-align: top; text-align: center; } .acltable .buttons input { display: block; width: 100px; } .acltable { border-spacing: 0; } .acltable td.list { padding: 0; } /***** autocomplete list *****/ #autocompletepane { background-color: white; border: 1px solid #d0d0d0; min-width: 351px; } #autocompletepane ul { margin: 0px; padding: 2px; list-style-image: none; list-style-type: none; } #autocompletepane ul li { display: block; height: 16px; font-size: 11px; padding-left: 6px; padding-top: 2px; padding-right: 6px; white-space: nowrap; cursor: pointer; } #autocompletepane ul li.selected { background-color: #d6efff; } /***** tabbed interface elements *****/ div.tabsbar { height: 22px; border-bottom: 1px solid #d0d0d0; white-space: nowrap; margin: 10px 5px 0 5px; } span.tablink, span.tablink-selected { float: left; height: 23px !important; height: 22px; overflow: hidden; background: url(images/tabs-left.gif) top left no-repeat; font-weight: bold; } span.tablink { cursor: pointer; text-shadow: white 1px 1px; } span.tablink-selected { cursor: default; background-position: 0px -23px; } span.tablink a, span.tablink-selected a { display: inline-block; padding: 4px 10px 0 5px; margin-left: 5px; height: 23px; color: #808080; max-width: 185px; text-decoration: none; overflow: hidden; text-overflow: ellipsis; -o-text-overflow: ellipsis; background: url(images/tabs-right.gif) top right no-repeat; outline: none; } span.tablink-selected a { cursor: inherit; color: #514949; background-position: right -23px; } fieldset.tabbed { margin-top: 0; padding-top: 12px; border-top: none; } /***** Dialog windows *****/ #modal_bg { background-color: #000; z-index: 1000; opacity: 0.2; filter: alpha(opacity=20); } #modal_pixel { z-index: 1001; } .modal { position: relative; min-width: 350px; overflow: hidden; line-height: 15px; background-color: #FFF; color: #333; border: 1px solid rgba(51, 51, 51, 0.5); border-radius: 4px; box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); } .modal_header { padding: 10px; font-size: 14px; font-weight: bold; border-bottom: solid 1px #DDD; } .modal_close { position: absolute; right: 10px; top: 10px; font-weight: normal; font-size: 12px; cursor: pointer; color: #BABABA; } .modal_msg { font-size: 12px; padding: 20px; color: #3A3A3A; text-shadow: rgba(255, 255, 255, 0.75) 0 1px 1px; } .modal_btns { padding: 10px; font-size: 10px; font-weight: bold; border-top: solid 1px #DDD; background-color: #EFEFEF; text-align: right; white-space: nowrap; } .modal_btns div { display: inline-block; min-width: 40px; padding: 0 10px; height: 25px; line-height: 25px; margin-left: 10px; text-align: center; cursor: pointer; border-radius: 4px; box-shadow: rgba(255, 255, 255, 0.2) 0px 1px 0px 0px inset, rgba(0, 0, 0, 0.0470588) 0px 1px 2px 0px; text-shadow: rgba(255, 255, 255, 0.75) 0 1px 1px; border: 1px solid rgba(0, 0, 0, 0.14902); border-bottom-color: rgba(0, 0, 0, 0.247059); background-color: #F5F5F5; color: #333; } .modal_btns div:hover { background-color: #E6E6E6; } .modal_btns div.default { text-shadow: rgba(0, 0, 0, 0.247059) 0px -1px 0px; border: 1px solid rgba(0, 0, 0, 0.0980392); background-color: #006DCC; color: #FFF; } .modal_btns div.default:hover { background-color: #0044CC } /**** ACI widget ********/ #aci-dialog, #aci-users-dialog { width: 550px; background-color: #F0F0F0; border: 1px solid #D0D0D0; border-radius: 4px; margin: 10px; padding: 10px; } #aci-users-dialog { width: 400px; } #aci-dialog fieldset { background-color: #f0f0f0; } #aci-dialog > label { padding-right: 5px; } #aci-name { width: 420px; } #aci-rights label, #aci-users-results, #aci-users-selected, #aci-targets-attr { display: block; } #aci-rights-all { padding-top: 5px; } #aci-targets-attr, #aci-users-results, #aci-users-selected, #aci-targets-target, #aci-targets-filter { width: 400px; } #aci-users-dialog label, #aci-targets label { font-size: 11px; font-color: #606060; } #aci-users-dialog div, #aci-targets div { margin-bottom: 10px; } /**** IMAP ACL widget ********/ #acl-dialog { width: 550px; background-color: #F0F0F0; border: 1px solid #D0D0D0; border-radius: 4px; margin: 10px; padding: 10px; } #acl-form td { white-space: nowrap; width: 1%; min-width: 20px; } #acl-form label { display: block; } #acl-form td.value div span { display: inline-block; vertical-align: top; min-width: 200px; } #acl-form td.value div label input, #acl-form td.value div label span { vertical-align: middle; } /**** Login form elements ****/ #login_form { margin: auto; margin-top: 75px; padding: 20px; width: 330px; background-color: #e0e0e0; border: 1px solid #d0d0d0; border-radius: 3px; -moz-border-radius: 3px; -webkit-border-radius: 3px; text-align: center; } #login_form span { display: block; line-height: 24px; width: 330px; text-align: left; white-space: nowrap; } #login_form label { display: block; width: 100px; text-align: right; float: left; margin-right: 10px; } #login_form select, #login_form input[type="text"], #login_form input[type="password"] { width: 210px; } #login_submit { margin-top: 5px; } /**** Main screen elements ****/ #main { padding: 5px 30px; } #main div { float: left; margin: 10px; width: 150px; height: 150px; text-align: center; cursor: pointer; background-color: #f0f0f0; border: 1px solid #d0d0d0; border-radius: 3px; -moz-border-radius: 3px; -webkit-border-radius: 3px; } #main div:hover { background-color: #d6efff; } #main div span.image { display: block; margin: 15px 27px 10px; height: 96px; width: 96px; background: url(images/about.png) center center no-repeat; } #main div.user span.image { background: url(images/users.png) center center no-repeat; } #main div.group span.image { background: url(images/groups.png) center center no-repeat; } #main div.resource span.image { background: url(images/resources.png) center center no-repeat; } #main div.sharedfolder span.image { background: url(images/sharedfolders.png) center center no-repeat; } #main div.settings span.image { background: url(images/settings.png) center center no-repeat; } #main div.domain span.image { background: url(images/domains.png) center center no-repeat; } #main div.role span.image { background: url(images/roles.png) center center no-repeat; } #main div.ou span.image { background: url(images/ou.png) center center no-repeat; } #main div span.label { font-weight: bold; } #reqtime { white-space: nowrap; vertical-align: top; } #domain-selector span.link { color: white; text-decoration: none; } /**** User/Group task elements ****/ #userlist table, #grouplist table { width: 100%; } /**** About pages ****/ td.yes { background-color: #B5FFAB; font-style: italic; text-align: center; } td.no { background-color: #FF0000; color: #FFFFFF; font-weight: bold; text-align: center; } /**** Settings ****/ table.form table.list td { padding: 2px 4px; } #type_attr_table td.actions { width: 40px; padding: 0; white-space: nowrap; } #type_attr_table thead td { white-space: nowrap; } #type_attr_table tfoot span { cursor: pointer; } #type_attr_table td.readonly { color: #514949; } #type_attr_form { display: none; } #type_attr_form table.form td { border: none; } a.add_attr { padding-left: 2px; padding-top: 2px; }