diff --git a/lib/kolab_client_task.php b/lib/kolab_client_task.php index 43e5fea..f266388 100644 --- a/lib/kolab_client_task.php +++ b/lib/kolab_client_task.php @@ -1,1751 +1,1751 @@ | +--------------------------------------------------------------------------+ | 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'); + $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, array('domain', 'group', 'resource', 'role', 'user'))) { if (!array_key_exists($task . "." . $idx, $caps)) { continue; } } if (strpos($idx, '.')) { $action = $idx; $class = preg_replace('/\.[a-z_-]+$/', '', $idx); } else { $action = $task . '.' . $idx; $class = $idx; } $menu[$idx] = sprintf('
  • ' .'%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'); + $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')", )); } // 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}')", )); } $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); } }