diff --git a/lib/kolab_client_task.php b/lib/kolab_client_task.php
index 0bc7f6e..4951630 100644
--- a/lib/kolab_client_task.php
+++ b/lib/kolab_client_task.php
@@ -1,1811 +1,1811 @@
 <?php
 /*
  +--------------------------------------------------------------------------+
  | This file is part of the Kolab Web Admin Panel                           |
  |                                                                          |
  | Copyright (C) 2011-2012, Kolab Systems AG                                |
  |                                                                          |
  | This program is free software: you can redistribute it and/or modify     |
  | it under the terms of the GNU Affero General Public License as published |
  | by the Free Software Foundation, either version 3 of the License, or     |
  | (at your option) any later version.                                      |
  |                                                                          |
  | This program is distributed in the hope that it will be useful,          |
  | but WITHOUT ANY WARRANTY; without even the implied warranty of           |
  | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the             |
  | GNU Affero General Public License for more details.                      |
  |                                                                          |
  | You should have received a copy of the GNU Affero General Public License |
  | along with this program. If not, see <http://www.gnu.org/licenses/>      |
  +--------------------------------------------------------------------------+
  | Author: Aleksander Machniak <machniak@kolabsys.com>                      |
  | Author: Jeroen van Meeuwen <vanmeeuwen@kolabsys.com>                     |
  +--------------------------------------------------------------------------+
 */
 
 
 class kolab_client_task
 {
     /**
      * @var kolab_client_output
      */
     protected $output;
 
     /**
      * @var kolab_client_api
      */
     protected $api;
 
     /**
      * @var Conf
      */
     protected $config;
 
     protected $ajax_only    = false;
     protected $page_title   = 'Kolab Web Admin Panel';
     protected $menu         = array();
     protected $cache        = array();
     protected $devel_mode   = false;
     protected $object_types = array('user', 'group', 'role', 'resource', 'sharedfolder', 'ou', 'domain');
     protected $page_size    = 20;
     protected $list_attribs = array();
     protected $list_module;
     protected $domain;
 
     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'] = isset($login['username']) ? trim($login['username']) : '';
                 $login['domain']   = isset($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 && !empty($_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('<li class="%s">'
                 .'<a href="#%s" onclick="return kadm.command(\'%s\', \'\', this)">%s</a></li>',
                 $class, $idx, $action, $this->translate($label));
         }
 
         return '<ul>' . implode("\n", $menu) . '</ul>';
     }
 
     /**
      * Adds watermark page definition into main page.
      */
     protected function watermark($name)
     {
         $this->output->command('set_watermark', $name);
     }
 
     /**
      * API GET request wrapper
      */
     protected function api_get($action, $get = array())
     {
         return $this->api_call('get', $action, $get);
     }
 
     /**
      * API POST request wrapper
      */
     protected function api_post($action, $get = array(), $post = array())
     {
         return $this->api_call('post', $action, $get, $post);
     }
 
     /**
      * API request wrapper with error handling
      */
     protected function api_call($type, $action, $get = array(), $post = array())
     {
         if ($type == 'post') {
             $result = $this->api->post($action, $get, $post);
         }
         else {
             $result = $this->api->get($action, $get);
         }
 
         // error handling
         if ($code = $result->get_error_code()) {
             // Invalid session, do logout
             if ($code == 403) {
                 $this->action_logout(true, false);
             }
 
             // Log communication errors, other should be logged on API side
             if ($code < 400) {
                 $this->raise_error($code, 'API Error: ' . $result->get_error_str());
             }
         }
 
         return $result;
     }
 
     /**
      * Returns list of object types.
      *
      * @para string  $type     Object type name
      * @param string $used_for Used_for attribute of object type
      *
      * @return array List of user types
      */
     protected function object_types($type, $used_for = null)
     {
         if (empty($type) || !in_array($type, $this->object_types)) {
             return array();
         }
 
         $cache_idx = $type . '_types' . ($used_for ? ":$used_for" : '');
 
         if (!array_key_exists($cache_idx, $this->cache)) {
             $result = $this->api_post($type . '_types.list');
             $list   = $result->get('list');
 
             if (!empty($used_for) && is_array($list)) {
                 foreach ($list as $type_id => $type_attrs) {
                     if ($type_attrs['used_for'] != $used_for) {
                         unset($list[$type_id]);
                     }
                 }
             }
 
             $this->cache[$cache_idx] = !empty($list) ? $list : array();
 
-            Log::trace("kolab_client_task::${type}_types() returns: " . var_export($list, true));
+            Log::trace("kolab_client_task::object_types('{$type}') 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 = !empty($this->domain) ? $this->domain : $_SESSION['user']['domain'];
 
         if (!$all) {
             return isset($list[$domain]) ? $list[$domain] : null;
         }
 
         return $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::div(array(
             'class'   => 'form-group input-group input-group-lg',
             'content' => kolab_html::label(array(
                     'for'     => 'login_name',
                     'class'   => 'font-icon login input-group-prepend input-group-text',
                     'content' => kolab_html::span(array(
                             'class'   => 'sr-only',
                             'content' => $this->translate('login.username'), true))))
                 . kolab_html::input(array(
                     'type'      => 'text',
                     'id'        => 'login_name',
                     'name'      => 'login[username]',
                     'value'     => isset($post['username']) ? $post['username'] : '',
                     'placeholder' => $this->translate('login.username'),
                     'autofocus' => true))
         ));
 
         $password = kolab_html::div(array(
             'class'   => 'form-group input-group input-group-lg',
             'content' => kolab_html::label(array(
                     'for'     => 'login_pass',
                     'class'   => 'font-icon password input-group-prepend input-group-text',
                     'content' => kolab_html::span(array(
                             'class'   => 'sr-only',
                             'content' => $this->translate('login.password'), true))))
                 . kolab_html::input(array(
                     'type'  => 'password',
                     'id'    => 'login_pass',
                     'name'  => 'login[password]',
                     'placeholder' => $this->translate('login.password'),
                     'value' => ''))
         ));
 
         $button = kolab_html::input(array(
             'type'  => 'submit',
             'id'    => 'login_submit',
             'class' => 'btn btn-primary btn-lg text-uppercase w-100',
             'value' => $this->translate('login.login')));
 
         $form = kolab_html::form(array(
             'id'     => 'login_form',
             'name'   => 'login',
             'method' => 'post',
             'action' => '?'),
             kolab_html::div(array('content' => $username))
             . kolab_html::div(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();
         $field_type = !empty($field['type']) ? $field['type'] : null;
 
         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['multiple'] = $field_type == 'multiselect';
 
             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;
             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';
 
             $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'
                     );
                 }
             }
 
             if (isset($field['maxlength'])) {
                 $result['maxlength'] = $field['maxlength'];
             }
         }
 
         $result['required'] = empty($field['optional']);
         $result['default']  = isset($field['default']) ? $field['default'] : null;
 
         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']]) ? $data[$field['name']] : (isset($resp['default']) ? $resp['default'] : null);
             $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
      * @param array  $custom_fields Extra fields definitions
      *
      * @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, $custom_fields = array())
     {
         $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 (!empty($type['is_default'])) {
                     $default = $type_id;
                     break;
                 }
             }
 
             reset($types);
 
             $data['type_id'] = $type = (isset($default) ? $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]);
             }
         }
 
         $fields += $custom_fields;
 
         // Get the rights on the entry and attribute level
         $data['effective_rights'] = $this->effective_rights($name, isset($data['id']) ? $data['id'] : null);
         $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 = '<pre class="debug form-control">' . $debug . '</pre>';
                 $fields['debug'] = array(
                         'label'   => 'debug',
                         'section' => 'system',
                         'value'   => $debug,
                     );
             }
         }
 
         // Add object type hidden field
         $fields['object_type'] = array(
             'section'  => 'system',
             'type'     => kolab_form::INPUT_HIDDEN,
             'value'    => $name,
         );
 
         // Get user-friendly names for lists
         foreach ($fields as $fname => $field) {
             if (!empty($field['data-autocomplete']) && !empty($data[$fname])) {
                 if (!is_array($data[$fname])) {
                     $data[$fname] = (array) $data[$fname];
                 }
 
                 // 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 (empty($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']) && isset($data[$idx]) && $data[$idx] !== '') {
                     $value = $data[$idx];
 
                     // Convert data for the list field with autocompletion
                     if (!empty($field['data-type']) && $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_' . (!empty($conf_aliases[$idx]) ? $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 (!empty($field['type']) && $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 (!empty($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')),
                 'class'   => 'btn btn-danger',
                 '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', !empty($data['entrydn']) ? $data['entrydn'] : null);
         $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") : (isset($data['cn']) ? $data['cn'] : null);
         }
 
         $form->set_title(kolab_html::escape($title));
 
         return $form;
     }
 
     /**
      * Check wether specified object can be deleted
      */
     protected function is_deletable($data)
     {
         return !empty($data['id']) && in_array('delete', (array) $data['effective_rights']['entry']);
     }
 
     /**
      * Search form.
      *
      * @return string HTML output of the form
      */
     public function search_form()
     {
         $attribs = $this->get_search_attribs();
 
         if (empty($attribs)) {
             return '';
         }
 
         $options = array();
         foreach (array_keys($attribs) as $key) {
             $options[$key] = kolab_html::escape($this->translate('search.' . $key));
         }
 
         $form = new kolab_form(array('id' => 'search-form'));
 
         $form->add_section('criteria', kolab_html::escape($this->translate('search.criteria')));
         $form->add_element(array(
             'section' => 'criteria',
             'label'   => $this->translate('search.field'),
             'name'    => 'field',
             'type'    => kolab_form::INPUT_SELECT,
             'options' => $options,
         ));
         $form->add_element(array(
             'section' => 'criteria',
             'label'   => $this->translate('search.method'),
             'name'    => 'method',
             'type'    => kolab_form::INPUT_SELECT,
             'options' => array(
                 'both'   => kolab_html::escape($this->translate('search.contains')),
                 'exact'  => kolab_html::escape($this->translate('search.is')),
                 'prefix' => kolab_html::escape($this->translate('search.prefix')),
             ),
         ));
 
         return $form->output();
     }
 
     /**
      * Returns list of attributes assigned to search field(s)
      *
      * @param string $name Optional search field name
      *
      * @return array List of attributes
      */
     protected function get_search_attribs($name = null)
     {
         $task    = $this->get_task();
         $types   = $this->object_types($task);
         $attribs = array();
 
         foreach ($this->search_attribs as $key => $value) {
             foreach ((array)$value as $idx => $attr) {
                 $found = false;
                 foreach ($types as $type) {
                     if (array_key_exists($attr, (array)$type['attributes']['auto_form_fields'])
                         || array_key_exists($attr, (array)$type['attributes']['form_fields'])
                         || array_key_exists($attr, (array)$type['attributes']['fields'])
                     ) {
                         $found = true;
                         break;
                     }
                 }
 
                 if (!$found) {
                     unset($value[$idx]);
                 }
             }
 
             if (!empty($value)) {
                 $attribs[$key] = $value;
             }
         }
 
         return $name ? $attribs[$name] : $attribs;
     }
 
     /**
      * Object list (and search handler)
      */
     public function action_list()
     {
         if (empty($this->list_attribs)) {
             return;
         }
 
         $task = $this->get_task();
         $page = (int) self::get_input('page', 'POST');
 
         if (!$page || $page < 1) {
             $page = 1;
         }
 
         // request parameters
         $post = array(
             'attributes' => $this->list_attribs,
             'sort_by'    => $this->list_attribs,
 //            'sort_order' => 'ASC',
             'page_size'  => $this->page_size,
             'page'       => $page,
         );
 
         // search parameters
         if (!empty($_POST['search'])) {
             $search = self::get_input('search', 'POST', true);
             $field  = self::get_input('field',  'POST');
             $method = self::get_input('method', 'POST');
             $attrs  = $this->get_search_attribs($field);
             $search_request = array();
 
             // Support attribute=value searches
             if ($task != 'settings' && preg_match('/^([a-z0-9]+)([~<>]*=)(.+)$/i', $search, $m)) {
                 $search_attr  = $m[1];
                 $search_type  = $m[2];
                 $search_value = $m[3];
 
                 if ($search_value === '*') {
                     $search_type  = 'exists';
                     $search_value = '';
                 }
                 else if ($search_value[0] === '*' && $search_value[strlen($search_value)-1] === '*') {
                     $search_type  = 'both';
                     $search_value = substr($search_value, 1, -1);
                 }
                 else if ($search_value[0] === '*') {
                     $search_type  = 'suffix';
                     $search_value = substr($search_value, 1);
                 }
                 else if ($search_value[strlen($search_value)-1] === '*') {
                     $search_type  = 'prefix';
                     $search_value = substr($search_value, 0, -1);
                 }
 
                 $search_request[$search_attr] = array(
                     'value' => $search_value,
                     'type'  => $search_type,
                 );
             }
             else {
                 foreach ($attrs as $attr) {
                     $search_request[$attr] = array(
                         'value' => $search,
                         'type'  => $method,
                     );
                 }
             }
         }
         else if (!empty($_POST['search_request'])) {
             $search_request = self::get_input('search_request', 'POST');
             $search_request = @unserialize(base64_decode($search_request));
         }
 
         if (!empty($search_request)) {
             $post['search']          = $search_request;
             $post['search_operator'] = 'OR';
         }
 
         // get users list
         $module = $this->list_module ? $this->list_module : $task . 's';
         $result = $this->api_post($module . '.list', null, $post);
         $count  = $result->get('count');
         $result = (array) $result->get('list');
 
         // calculate records
         if ($count) {
             $start = 1 + max(0, $page - 1) * $this->page_size;
             $end   = min($start + $this->page_size - 1, $count);
         }
 
         $rows = $head = $foot = array();
         $cols = array('name');
         $i    = 0;
 
         $table_class = 'list';
         $foot_body = '';
 
         // 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 font-icon' . ($prev ? '' : ' disabled'),
                 'href'  => '#',
                 'onclick' => $prev ? "kadm.command('$task.list', {page: $prev})" : "return false",
             ));
             $next = kolab_html::a(array(
                 'class' => 'next font-icon' . ($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 . ' table table-sm',
             'head'  => $head,
             'body'  => $rows,
             'foot'  => $foot,
         ));
 
         if ($this->action == 'list') {
             $this->output->command('set_watermark', 'taskcontent');
         }
 
         $this->output->set_env('search_request', !empty($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);
     }
 }