diff --git a/plugins/kolab_delegation/kolab_delegation_engine.php b/plugins/kolab_delegation/kolab_delegation_engine.php
index bb6a71b8..b5ad4a6c 100644
--- a/plugins/kolab_delegation/kolab_delegation_engine.php
+++ b/plugins/kolab_delegation/kolab_delegation_engine.php
@@ -1,968 +1,968 @@
 <?php
 
 /**
  * Kolab Delegation Engine
  *
  * @version @package_version@
  * @author Thomas Bruederli <bruederli@kolabsys.com>
  * @author Aleksander Machniak <machniak@kolabsys.com>
  *
  * Copyright (C) 2011-2012, Kolab Systems AG <contact@kolabsys.com>
  *
  * 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/>.
  */
 
 class kolab_delegation_engine
 {
     public $context;
 
     private $rc;
     private $ldap;
     private $ldap_filter;
     private $ldap_delegate_field;
     private $ldap_login_field;
     private $ldap_name_field;
     private $ldap_email_field;
     private $ldap_org_field;
     private $ldap_dn;
     private $cache = array();
     private $folder_types = array('mail', 'event', 'task');
 
     const ACL_READ  = 1;
     const ACL_WRITE = 2;
 
     /**
      * Class constructor
      */
     public function __construct()
     {
         $this->rc = rcube::get_instance();
     }
 
     /**
      * Add delegate
      *
      * @param string|array $delegate Delegate DN (encoded) or delegate data (result of delegate_get())
      * @param array        $acl      List of folder->right map
      *
      * @return string On error returns an error label, on success returns null
      */
     public function delegate_add($delegate, $acl)
     {
         if (!is_array($delegate)) {
             $delegate = $this->delegate_get($delegate);
         }
 
         $dn = $delegate['ID'];
         if (empty($delegate) || empty($dn)) {
             return 'createerror';
         }
 
         $list = $this->list_delegates();
         $list = array_keys((array)$list);
         $list = array_filter($list);
 
         if (in_array($dn, $list)) {
             return 'delegationexisterror';
         }
 
         // add delegate to the list
         $list[] = $dn;
         $list   = array_map(array('kolab_ldap', 'dn_decode'), $list);
 
         // update user record
         $result = $this->user_update_delegates($list);
 
         // Set ACL on folders
         if ($result && !empty($acl)) {
             $this->delegate_acl_update($delegate['uid'], $acl);
         }
 
         return $result ? null : 'createerror';
     }
 
     /**
      * Set/Update ACL on delegator's folders
      *
      * @param string $uid    Delegate authentication identifier
      * @param array  $acl    List of folder->right map
      * @param bool   $update Update (remove) old rights
      *
      * @return string On error returns an error label, on success returns null
      */
     public function delegate_acl_update($uid, $acl, $update = false)
     {
         $storage     = $this->rc->get_storage();
         $right_types = $this->right_types();
         $folders     = $update ? $this->list_folders($uid) : array();
 
         foreach ($acl as $folder_name => $rights) {
             $r = $right_types[$rights];
             if ($r) {
                 $storage->set_acl($folder_name, $uid, $r);
             }
             else {
                 $storage->delete_acl($folder_name, $uid);
             }
 
             if (!empty($folders) && isset($folders[$folder_name])) {
                 unset($folders[$folder_name]);
             }
         }
 
         foreach ($folders as $folder_name => $folder) {
             if ($folder['rights']) {
                 $storage->delete_acl($folder_name, $uid);
             }
         }
     }
 
     /**
      * Delete delgate
      *
      * @param string $dn      Delegate DN (encoded)
      * @param bool   $acl_del Enable ACL deletion on delegator folders
      *
      * @return string On error returns an error label, on success returns null
      */
     public function delegate_delete($dn, $acl_del = false)
     {
         $delegate = $this->delegate_get($dn);
         $list     = $this->list_delegates();
         $user     = $this->user();
 
         if (empty($delegate) || !isset($list[$dn])) {
             return 'deleteerror';
         }
 
         // remove delegate from the list
         unset($list[$dn]);
         $list = array_keys($list);
         $list = array_map(array('kolab_ldap', 'dn_decode'), $list);
         $user[$this->ldap_delegate_field] = $list;
 
         // update user record
         $result = $this->user_update_delegates($list);
 
         // remove ACL
         if ($result && $acl_del) {
             $this->delegate_acl_update($delegate['uid'], array(), true);
         }
 
         return $result ? null : 'deleteerror';
     }
 
     /**
      * Return delegate data
      *
      * @param string $dn Delegate DN (encoded)
      *
      * @return array Delegate record (ID, name, uid, imap_uid)
      */
     public function delegate_get($dn)
     {
         // use internal cache so we not query LDAP more than once per request
         if (!isset($this->cache[$dn])) {
             $ldap = $this->ldap();
 
             if (!$ldap || empty($dn)) {
                 return array();
             }
 
             // Get delegate
             $user = $ldap->get_record(kolab_ldap::dn_decode($dn));
 
             if (empty($user)) {
                 return array();
             }
 
             $delegate = $this->parse_ldap_record($user);
             $delegate['ID'] = $dn;
 
             $this->cache[$dn] = $delegate;
         }
 
         return $this->cache[$dn];
     }
 
     /**
      * Return delegate data
      *
      * @param string $login Delegate name (the 'uid' returned in get_users())
      *
      * @return array Delegate record (ID, name, uid, imap_uid)
      */
     public function delegate_get_by_name($login)
     {
         $ldap = $this->ldap();
 
         if (!$ldap || empty($login)) {
             return array();
         }
 
         $list = $ldap->dosearch($this->ldap_login_field, $login, 1);
 
         if (count($list) == 1) {
             $dn   = key($list);
             $user = $list[$dn];
 
             return $this->parse_ldap_record($user, $dn);
         }
     }
 
     /**
      * LDAP object getter
      */
     private function ldap()
     {
         if ($this->ldap !== null) {
             return $this->ldap;
         }
 
         $this->ldap = kolab_storage::ldap('kolab_delegation_addressbook');
 
         if (!$this->ldap || !$this->ldap->ready) {
             return null;
         }
 
         // Default filter of LDAP queries
         $this->ldap_filter = $this->rc->config->get('kolab_delegation_filter', '(|(objectClass=kolabInetOrgPerson)(&(objectclass=kolabsharedfolder)(kolabFolderType=mail)))');
         // Name of the LDAP field for delegates list
         $this->ldap_delegate_field = $this->rc->config->get('kolab_delegation_delegate_field', 'kolabDelegate');
         // Encoded LDAP DN of current user, set on login by kolab_auth plugin
         $this->ldap_dn = $_SESSION['kolab_dn'];
 
         // Name of the LDAP field with authentication ID
         $this->ldap_login_field = $this->rc->config->get('kolab_delegation_login_field', $this->rc->config->get('kolab_auth_login'));
         // Name of the LDAP field with user name used for identities
         $this->ldap_name_field = $this->rc->config->get('kolab_delegation_name_field', $this->rc->config->get('kolab_auth_name'));
         // Name of the LDAP field with email addresses used for identities
         $this->ldap_email_field = $this->rc->config->get('kolab_delegation_email_field', $this->rc->config->get('kolab_auth_email'));
         // Name of the LDAP field with organization name for identities
         $this->ldap_org_field = $this->rc->config->get('kolab_delegation_organization_field', $this->rc->config->get('kolab_auth_organization'));
 
         $this->ldap->set_filter($this->ldap_filter);
         $this->ldap->extend_fieldmap(array($this->ldap_delegate_field => $this->ldap_delegate_field));
 
         return $this->ldap;
     }
 
     /**
      * List current user delegates
      */
     public function list_delegates()
     {
         $result = array();
         $ldap   = $this->ldap();
         $user   = $this->user();
 
         if (empty($ldap) || empty($user)) {
             return array();
         }
 
         // Get delegates of current user
-        $delegates = $user[$this->ldap_delegate_field];
+        $delegates = $user[$this->ldap_delegate_field] ?? null;
 
         if (!empty($delegates)) {
             foreach ((array)$delegates as $dn) {
                 $delegate = $ldap->get_record($dn);
                 $data     = $this->parse_ldap_record($delegate, $dn);
 
                 if (!empty($data) && !empty($data['name'])) {
                     $result[$data['ID']] = $data['name'];
                 }
             }
         }
 
         return $result;
     }
 
     /**
      * List current user delegators
      *
      * @return array List of delegators
      */
     public function list_delegators()
     {
         $result = array();
         $ldap   = $this->ldap();
 
         if (empty($ldap) || empty($this->ldap_dn)) {
             return array();
         }
 
         $list = $ldap->dosearch($this->ldap_delegate_field, $this->ldap_dn, 1);
 
         foreach ($list as $dn => $delegator) {
             $delegator = $this->parse_ldap_record($delegator, $dn);
             $result[$delegator['ID']] = $delegator;
         }
 
         return $result;
     }
 
     /**
      * List current user delegators in format compatible with Calendar plugin
      *
      * @return array List of delegators
      */
     public function list_delegators_js()
     {
         $list   = $this->list_delegators();
         $result = array();
 
         foreach ($list as $delegator) {
             $name = $delegator['name'];
             if ($pos = strrpos($name, '(')) {
                 $name = trim(substr($name, 0, $pos));
             }
 
             $result[$delegator['imap_uid']] = array(
                 'emails' => ';' . implode(';', $delegator['email']),
                 'email'  => $delegator['email'][0],
                 'name'   => $name,
             );
         }
 
         return $result;
     }
 
     /**
      * Prepare namespace prefixes for JS environment
      *
      * @return array List of prefixes
      */
     public function namespace_js()
     {
         $storage = $this->rc->get_storage();
         $ns      = $storage->get_namespace('other');
 
         if ($ns) {
             foreach ($ns as $idx => $nsval) {
                 $ns[$idx] = kolab_storage::folder_id($nsval[0]);
             }
         }
 
         return $ns;
     }
 
     /**
      * Get all folders to which current user has admin access
      *
      * @param string $delegate IMAP user identifier
      *
      * @return array Folder type/rights
      */
     public function list_folders($delegate = null)
     {
         $storage  = $this->rc->get_storage();
         $folders  = $storage->list_folders();
         $metadata = kolab_storage::folders_typedata();
         $result   = array();
 
         if (!is_array($metadata)) {
             return $result;
         }
 
         // Definition of read and write ACL
         $right_types = $this->right_types();
 
         $delegate_lc = strtolower($delegate);
 
         foreach ($folders as $folder) {
             // get only folders in personal namespace
             if ($storage->folder_namespace($folder) != 'personal') {
                 continue;
             }
 
             $rights = null;
             $type   = $metadata[$folder] ?: 'mail';
             list($class, $subclass) = explode('.', $type);
 
             if (!in_array($class, $this->folder_types)) {
                 continue;
             }
 
             // in edit mode, get folder ACL
             if ($delegate) {
                 // @TODO: cache ACL
                 $imap_acl = $storage->get_acl($folder);
                 if (!empty($imap_acl) && (($acl = $imap_acl[$delegate]) || ($acl = $imap_acl[$delegate_lc]))) {
                     if ($this->acl_compare($acl, $right_types[self::ACL_WRITE])) {
                         $rights = self::ACL_WRITE;
                     }
                     else if ($this->acl_compare($acl, $right_types[self::ACL_READ])) {
                         $rights = self::ACL_READ;
                     }
                 }
             }
             else if ($folder == 'INBOX' || $subclass == 'default' || $subclass == 'inbox') {
                 $rights = self::ACL_WRITE;
             }
 
             $result[$folder] = array(
                 'type'   => $class,
                 'rights' => $rights,
             );
         }
 
         return $result;
     }
 
     /**
      * Returns list of users for autocompletion
      *
      * @param string $search Search string
      *
      * @return array Users list
      */
     public function list_users($search)
     {
         $ldap = $this->ldap();
 
         if (empty($ldap) || $search === '' || $search === null) {
             return array();
         }
 
         $max    = (int) $this->rc->config->get('autocomplete_max', 15);
         $mode   = (int) $this->rc->config->get('addressbook_search_mode');
         $fields = array_unique(array_filter(array_merge((array)$this->ldap_name_field, (array)$this->ldap_login_field)));
         $users  = array();
         $keys   = array();
 
         $result = $ldap->dosearch($fields, $search, $mode, (array)$this->ldap_login_field, $max);
 
         foreach ($result as $record) {
             // skip self
             if ($record['dn'] == $_SESSION['kolab_dn']) {
                 continue;
             }
 
             $user = $this->parse_ldap_record($record);
 
             if ($user['uid']) {
                 $display = rcube_addressbook::compose_search_name($record);
                 $user    = array('name' => $user['uid'], 'display' => $display);
                 $users[] = $user;
                 $keys[]  = $display ?: $user['uid'];
             }
         }
 
         if (count($users)) {
             // sort users index
             asort($keys, SORT_LOCALE_STRING);
             // re-sort users according to index
             foreach (array_keys($keys) as $idx) {
                 $keys[$idx] = $users[$idx];
             }
             $users = array_values($keys);
         }
 
         return $users;
     }
 
     /**
      * Extract delegate identifiers and pretty name from LDAP record
      */
     private function parse_ldap_record($data, $dn = null)
     {
         $email = array();
         $uid   = $data[$this->ldap_login_field];
 
         if (is_array($uid)) {
             $uid = array_filter($uid);
             $uid = $uid[0];
         }
 
         // User name for identity
         foreach ((array)$this->ldap_name_field as $field) {
             $name = is_array($data[$field]) ? $data[$field][0] : $data[$field];
             if (!empty($name)) {
                 break;
             }
         }
 
         // User email(s) for identity
         foreach ((array)$this->ldap_email_field as $field) {
             $user_email = is_array($data[$field]) ? array_filter($data[$field]) : $data[$field];
             if (!empty($user_email)) {
                 $email = array_merge((array)$email, (array)$user_email);
             }
         }
 
         // Organization for identity
         foreach ((array)$this->ldap_org_field as $field) {
             $organization = is_array($data[$field]) ? $data[$field][0] : $data[$field];
             if (!empty($organization)) {
                 break;
             }
         }
 
         $realname = $name;
         if ($uid && $name) {
             $name .= ' (' . $uid . ')';
         }
         else {
             $name = $uid;
         }
 
         // get IMAP uid - identifier used in shared folder hierarchy
         $imap_uid = $uid;
         if ($pos = strpos($imap_uid, '@')) {
             $imap_uid = substr($imap_uid, 0, $pos);
         }
 
         return array(
             'ID'       => kolab_ldap::dn_encode($dn),
             'uid'      => $uid,
             'name'     => $name,
             'realname' => $realname,
             'imap_uid' => $imap_uid,
             'email'    => $email,
             'organization' => $organization,
         );
     }
 
     /**
      * Returns LDAP record of current user
      *
      * @return array User data
      */
     public function user($parsed = false)
     {
         if (!isset($this->cache['user'])) {
             $ldap = $this->ldap();
 
             if (!$ldap) {
                 return array();
             }
 
             // Get current user record
             $this->cache['user'] = $ldap->get_record($this->ldap_dn);
         }
 
         return $parsed ? $this->parse_ldap_record($this->cache['user']) : $this->cache['user'];
     }
 
     /**
      * Update LDAP record of current user
      *
      * @param array List of delegates
      */
     public function user_update_delegates($list)
     {
         $ldap = $this->ldap();
         $pass = $this->rc->decrypt($_SESSION['password']);
 
         if (!$ldap) {
             return false;
         }
 
         // need to bind as self for sufficient privilages
         if (!$ldap->bind($this->ldap_dn, $pass)) {
             return false;
         }
 
         $user[$this->ldap_delegate_field] = $list;
 
         unset($this->cache['user']);
 
         // replace delegators list in user record
         return $ldap->replace($this->ldap_dn, $user);
     }
 
     /**
      * Manage delegation data on user login
      */
     public function delegation_init()
     {
         // Fetch all delegators from LDAP who assigned the
         // current user as their delegate and create identities
         //  a) if identity with delegator's email exists, continue
         //  b) create identity ($delegate on behalf of $delegator
         //        <$delegator-email>) for new delegators
         //  c) remove all other identities which do not match the user's primary
         //       or alias email if 'kolab_delegation_purge_identities' is set.
 
         $delegators = $this->list_delegators();
         $use_subs   = $this->rc->config->get('kolab_use_subscriptions');
         $identities = $this->rc->user->list_emails();
         $emails     = array();
         $uids       = array();
 
         if (!empty($delegators)) {
             $storage  = $this->rc->get_storage();
             $other_ns = $storage->get_namespace('other') ?: array();
             $folders  = $storage->list_folders();
         }
 
         // convert identities to simpler format for faster access
         foreach ($identities as $idx => $ident) {
             // get user name from default identity
             if (!$idx) {
                 $default = array(
                     'name' => $ident['name'],
                 );
             }
             $emails[$ident['identity_id']] = $ident['email'];
         }
 
         // for every delegator...
         foreach ($delegators as $delegator) {
             $uids[$delegator['imap_uid']] = $email_arr = $delegator['email'];
             $diff = array_intersect($emails, $email_arr);
 
             // identity with delegator's email already exist, do nothing
             if (count($diff)) {
                 $emails = array_diff($emails, $email_arr);
                 continue;
             }
 
             // create identities for delegator emails
             foreach ($email_arr as $email) {
                 // @TODO: "Delegatorname" or "Username on behalf of Delegatorname"?
                 $default['name']  = $delegator['realname'];
                 $default['email'] = $email;
                 // Database field for organization is NOT NULL
                 $default['organization'] = empty($delegator['organization']) ? '' : $delegator['organization'];
                 $this->rc->user->insert_identity($default);
             }
 
             // IMAP folders shared by new delegators shall be subscribed on login,
             // as well as existing subscriptions of previously shared folders shall
             // be removed. I suppose the latter one is already done in Roundcube.
 
             // for every accessible folder...
             foreach ($folders as $folder) {
                 // for every 'other' namespace root...
                 foreach ($other_ns as $ns) {
                     $prefix = $ns[0] . $delegator['imap_uid'];
                     // subscribe delegator's folder
                     if ($folder === $prefix || strpos($folder, $prefix . substr($ns[0], -1)) === 0) {
                         // Event/Task folders need client-side activation
                         $type = kolab_storage::folder_type($folder);
                         if (preg_match('/^(event|task)/i', $type)) {
                             kolab_storage::folder_activate($folder);
                         }
                         // Subscribe to mail folders and (if system is configured
                         // to display only subscribed folders) to other
                         if ($use_subs || preg_match('/^mail/i', $type)) {
                             $storage->subscribe($folder);
                         }
                     }
                 }
             }
         }
 
         // remove identities that "do not belong" to user nor delegators
         if ($this->rc->config->get('kolab_delegation_purge_identities')) {
             $user   = $this->user(true);
             $emails = array_diff($emails, $user['email']);
 
             foreach (array_keys($emails) as $idx) {
                 $this->rc->user->delete_identity($idx);
             }
         }
 
         $_SESSION['delegators'] = $uids;
     }
 
     /**
      * Sets delegator context according to email message recipient
      *
      * @param rcube_message $message Email message object
      */
     public function delegator_context_from_message($message)
     {
         if (empty($_SESSION['delegators'])) {
             return;
         }
 
         // Match delegators' addresses with message To: address
         // @TODO: Is this reliable enough?
         // Roundcube sends invitations to every attendee separately,
         // but maybe there's a software which sends with CC header or many addresses in To:
 
         $emails = $message->get_header('to');
         $emails = rcube_mime::decode_address_list($emails, null, false);
 
         foreach ($emails as $email) {
             foreach ($_SESSION['delegators'] as $uid => $addresses) {
                 if (in_array($email['mailto'], $addresses)) {
                     return $this->context = $uid;
                 }
             }
         }
     }
 
     /**
      * Return (set) current delegator context
      *
      * @return string Delegator UID
      */
     public function delegator_context()
     {
         if (!$this->context && !empty($_SESSION['delegators'])) {
             $context = rcube_utils::get_input_value('_context', rcube_utils::INPUT_GPC);
             if ($context && isset($_SESSION['delegators'][$context])) {
                 $this->context = $context;
             }
         }
 
         return $this->context;
     }
 
     /**
      * Set user identity according to delegator delegator
      *
      * @param array $args Reference to plugin hook arguments
      */
     public function delegator_identity_filter(&$args)
     {
         $context = $this->delegator_context();
 
         if (!$context) {
             return;
         }
 
         $identities = $this->rc->user->list_emails();
         $emails     = $_SESSION['delegators'][$context];
 
         foreach ($identities as $ident) {
             if (in_array($ident['email'], $emails)) {
                 $args['identity'] = $ident;
                 return;
             }
         }
 
         // fallback to default identity
         $args['identity'] = array_shift($identities);
     }
 
     /**
      * Filter user emails according to delegator context
      *
      * @param array $args Reference to plugin hook arguments
      */
     public function delegator_emails_filter(&$args)
     {
         $context = $this->delegator_context();
 
         // try to derive context from the given user email
         if (!$context && !empty($args['emails'])) {
             if (($user = preg_replace('/@.+$/', '', $args['emails'][0])) && isset($_SESSION['delegators'][$user])) {
                 $context = $user;
             }
         }
 
         // return delegator's addresses
         if ($context) {
             $args['emails'] = $_SESSION['delegators'][$context];
             $args['abort']  = true;
         }
         // return only user addresses (exclude all delegators addresses)
         else if (!empty($_SESSION['delegators'])) {
             $identities = $this->rc->user->list_emails();
             $emails[]   = $this->rc->user->get_username();
 
             foreach ($identities as $identity) {
                 $emails[] = $identity['email'];
             }
 
             foreach ($_SESSION['delegators'] as $delegator_emails) {
                 $emails = array_diff($emails, $delegator_emails);
             }
 
             $args['emails'] = array_unique($emails);
             $args['abort']  = true;
         }
     }
 
     /**
      * Filters list of calendar/task folders according to delegator context
      *
      * @param array $args Reference to plugin hook arguments
      */
     public function delegator_folder_filter(&$args, $mode = 'calendars')
     {
         $context = $this->delegator_context();
 
         if (empty($context)) {
             return $args;
         }
 
         $storage  = $this->rc->get_storage();
         $other_ns = $storage->get_namespace('other') ?: array();
         $delim    = $storage->get_hierarchy_delimiter();
 
         if ($mode == 'calendars') {
             $editable = $args['filter'] & calendar_driver::FILTER_WRITEABLE;
             $active   = $args['filter'] & calendar_driver::FILTER_ACTIVE;
             $personal = $args['filter'] & calendar_driver::FILTER_PERSONAL;
             $shared   = $args['filter'] & calendar_driver::FILTER_SHARED;
         }
         else {
             $editable = $args['filter'] & tasklist_driver::FILTER_WRITEABLE;
             $active   = $args['filter'] & tasklist_driver::FILTER_ACTIVE;
             $personal = $args['filter'] & tasklist_driver::FILTER_PERSONAL;
             $shared   = $args['filter'] & tasklist_driver::FILTER_SHARED;
         }
 
         $folders = array();
 
         foreach ($args['list'] as $folder) {
             if (isset($folder->ready) && !$folder->ready) {
                 continue;
             }
 
             if ($editable && !$folder->editable) {
                 continue;
             }
 
             if ($active && !$folder->storage->is_active()) {
                 continue;
             }
 
             if ($personal || $shared) {
                 $ns = $folder->get_namespace();
 
                 if ($personal && $ns == 'personal') {
                     continue;
                 }
                 else if ($personal && $ns == 'other') {
                     $found = false;
                     foreach ($other_ns as $ns) {
                         $c_folder = $ns[0] . $context . $delim;
                         if (strpos($folder->name, $c_folder) === 0) {
                             $found = true;
                         }
                     }
 
                     if (!$found) {
                         continue;
                     }
                 }
                 else if (!$shared || $ns != 'shared') {
                     continue;
                 }
             }
 
             $folders[$folder->id] = $folder;
         }
 
         $args[$mode]   = $folders;
         $args['abort'] = true;
     }
 
     /**
      * Filters/updates message headers according to delegator context
      *
      * @param array $args Reference to plugin hook arguments
      */
     public function delegator_delivery_filter(&$args)
     {
         // no context, but message still can be send on behalf of...
         if (!empty($_SESSION['delegators'])) {
             $message = $args['message'];
             $headers = $message->headers();
 
             // get email address from From: header
             $from = rcube_mime::decode_address_list($headers['From']);
             $from = array_shift($from);
             $from = $from['mailto'];
 
             foreach ($_SESSION['delegators'] as $uid => $addresses) {
                 if (in_array($from, $addresses)) {
                     $context = $uid;
                     break;
                 }
             }
 
             // add Sender: header with current user default identity
             if ($context) {
                 $identity = $this->rc->user->get_identity();
                 $sender   = format_email_recipient($identity['email'], $identity['name']);
 
                 $message->headers(array('Sender' => $sender), false, true);
             }
         }
     }
 
     /**
      * Compares two ACLs (according to supported rights)
      *
      * @param array $acl1 ACL rights array (or string)
      * @param array $acl2 ACL rights array (or string)
      *
      * @param bool True if $acl1 contains all rights from $acl2
      */
     function acl_compare($acl1, $acl2)
     {
         if (!is_array($acl1)) $acl1 = str_split($acl1);
         if (!is_array($acl2)) $acl2 = str_split($acl2);
 
         $rights = $this->rights_supported();
 
         $acl1 = array_intersect($acl1, $rights);
         $acl2 = array_intersect($acl2, $rights);
         $res  = array_intersect($acl1, $acl2);
 
         $cnt1 = count($res);
         $cnt2 = count($acl2);
 
         if ($cnt1 >= $cnt2) {
             return true;
         }
     }
 
     /**
      * Get list of supported access rights (according to RIGHTS capability)
      *
      * @todo: this is stolen from acl plugin, move to rcube_storage/rcube_imap
      *
      * @return array List of supported access rights abbreviations
      */
     public function rights_supported()
     {
         if ($this->supported !== null) {
             return $this->supported;
         }
 
         $storage = $this->rc->get_storage();
         $capa    = $storage->get_capability('RIGHTS');
 
         if (is_array($capa)) {
             $rights = strtolower($capa[0]);
         }
         else {
             $rights = 'cd';
         }
 
         return $this->supported = str_split('lrswi' . $rights . 'pa');
     }
 
     private function right_types()
     {
         // Get supported rights and build column names
         $supported = $this->rights_supported();
 
         // depending on server capability either use 'te' or 'd' for deleting msgs
         $deleteright = implode(array_intersect(str_split('ted'), $supported));
 
         return array(
             self::ACL_READ  => 'lrs',
             self::ACL_WRITE => 'lrswi'.$deleteright,
         );
     }
 }
diff --git a/plugins/libkolab/lib/kolab_ldap.php b/plugins/libkolab/lib/kolab_ldap.php
index d7266f8c..c51fede4 100644
--- a/plugins/libkolab/lib/kolab_ldap.php
+++ b/plugins/libkolab/lib/kolab_ldap.php
@@ -1,613 +1,613 @@
 <?php
 
 /**
  * Kolab Authentication and User Base
  *
  * @author Aleksander Machniak <machniak@kolabsys.com>
  *
  * Copyright (C) 2011-2019, Kolab Systems AG <contact@kolabsys.com>
  *
  * 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/>.
  */
 
 /**
  * Wrapper class for rcube_ldap_generic
  */
 class kolab_ldap extends rcube_ldap_generic
 {
     private $conf     = array();
     private $fieldmap = array();
     private $rcache;
 
 
     function __construct($p)
     {
         $rcmail = rcube::get_instance();
 
         $this->conf = $p;
         $this->conf['kolab_auth_user_displayname'] = $rcmail->config->get('kolab_auth_user_displayname', '{name}');
 
         $this->fieldmap = $p['fieldmap'];
         $this->fieldmap['uid'] = 'uid';
 
         $p['attributes'] = array_values($this->fieldmap);
         $p['debug']      = (bool) $rcmail->config->get('ldap_debug');
 
         if ($cache_type = $rcmail->config->get('ldap_cache', 'db')) {
             $cache_ttl   = $rcmail->config->get('ldap_cache_ttl', '10m');
             $this->cache = $rcmail->get_cache('LDAP.kolab_cache', $cache_type, $cache_ttl);
         }
 
         // Connect to the server (with bind)
         parent::__construct($p);
         $this->_connect();
 
         $rcmail->add_shutdown_function(array($this, 'close'));
     }
 
     /**
     * Establish a connection to the LDAP server
     */
     private function _connect()
     {
         // try to connect + bind for every host configured
         // with OpenLDAP 2.x ldap_connect() always succeeds but ldap_bind will fail if host isn't reachable
         // see http://www.php.net/manual/en/function.ldap-connect.php
         foreach ((array)$this->config['hosts'] as $host) {
             // skip host if connection failed
             if (!$this->connect($host)) {
                 continue;
             }
 
             $bind_pass      = $this->config['bind_pass'] ?? null;
             $bind_user      = $this->config['bind_user'] ?? null;
             $bind_dn        = $this->config['bind_dn'];
             $base_dn        = $this->config['base_dn'];
             $groups_base_dn = $this->config['groups']['base_dn'] ?: $base_dn;
 
             // User specific access, generate the proper values to use.
             if ($this->config['user_specific']) {
                 $rcube = rcube::get_instance();
 
                 // No password set, use the session password
                 if (empty($bind_pass)) {
                     $bind_pass = $rcube->get_user_password();
                 }
 
                 $u = null;
                 // Get the pieces needed for variable replacement.
                 if ($fu = ($rcube->get_user_email() ?: ($this->config['username'] ?? null))) {
                     list($u, $d) = explode('@', $fu);
                 }
                 else {
                     $d = $this->config['mail_domain'] ?? null;
                 }
 
                 $dc = 'dc=' . strtr($d, array('.' => ',dc=')); // hierarchal domain string
 
                 // resolve $dc through LDAP
                 if (!empty($this->config['domain_filter']) && !empty($this->config['search_bind_dn'])) {
                     $this->bind($this->config['search_bind_dn'], $this->config['search_bind_pw']);
                     $dc = $this->domain_root_dn($d);
                 }
 
                 $replaces = array('%dn' => '', '%dc' => $dc, '%d' => $d, '%fu' => $fu, '%u' => $u);
 
                 // Search for the dn to use to authenticate
-                if ($this->config['search_base_dn'] && $this->config['search_filter']
+                if (($this->config['search_base_dn'] ?? false) && ($this->config['search_filter'] ?? false)
                     && (strstr($bind_dn, '%dn') || strstr($base_dn, '%dn') || strstr($groups_base_dn, '%dn'))
                 ) {
                     $search_attribs = array('uid');
                     if ($search_bind_attrib = (array) $this->config['search_bind_attrib']) {
                         foreach ($search_bind_attrib as $r => $attr) {
                             $search_attribs[] = $attr;
                             $replaces[$r] = '';
                         }
                     }
 
                     $search_bind_dn = strtr($this->config['search_bind_dn'], $replaces);
                     $search_base_dn = strtr($this->config['search_base_dn'], $replaces);
                     $search_filter  = strtr($this->config['search_filter'], $replaces);
 
                     $cache_key = 'DN.' . md5("$host:$search_bind_dn:$search_base_dn:$search_filter:" . $this->config['search_bind_pw']);
 
                     if ($this->cache && ($dn = $this->cache->get($cache_key))) {
                         $replaces['%dn'] = $dn;
                     }
                     else {
                         $ldap = $this;
                         if (!empty($search_bind_dn) && !empty($this->config['search_bind_pw'])) {
                             // To protect from "Critical extension is unavailable" error
                             // we need to use a separate LDAP connection
                             if (!empty($this->config['vlv'])) {
                                 $ldap = new rcube_ldap_generic($this->config);
                                 $ldap->config_set(array('cache' => $this->cache, 'debug' => $this->debug));
                                 if (!$ldap->connect($host)) {
                                     continue;
                                 }
                             }
 
                             if (!$ldap->bind($search_bind_dn, $this->config['search_bind_pw'])) {
                                 continue;  // bind failed, try next host
                             }
                         }
 
                         $res = $ldap->search($search_base_dn, $search_filter, 'sub', $search_attribs);
                         if ($res) {
                             $res->rewind();
                             $replaces['%dn'] = key($res->entries(true));
 
                             // add more replacements from 'search_bind_attrib' config
                             if ($search_bind_attrib) {
                                 $res = $res->current();
                                 foreach ($search_bind_attrib as $r => $attr) {
                                     $replaces[$r] = $res[$attr][0];
                                 }
                             }
                         }
 
                         if ($ldap != $this) {
                             $ldap->close();
                         }
                     }
 
                     // DN not found
                     if (empty($replaces['%dn'])) {
                         if (!empty($this->config['search_dn_default']))
                             $replaces['%dn'] = $this->config['search_dn_default'];
                         else {
                             rcube::raise_error(array(
                                 'code' => 100, 'type' => 'ldap',
                                 'file' => __FILE__, 'line' => __LINE__,
                                 'message' => "DN not found using LDAP search."), true);
                             continue;
                         }
                     }
 
                     if ($this->cache && !empty($replaces['%dn'])) {
                         $this->cache->set($cache_key, $replaces['%dn']);
                     }
                 }
 
                 // Replace the bind_dn and base_dn variables.
                 $bind_dn        = strtr($bind_dn, $replaces);
                 $base_dn        = strtr($base_dn, $replaces);
                 $groups_base_dn = strtr($groups_base_dn, $replaces);
 
                 // replace placeholders in filter settings
                 if (!empty($this->config['filter'])) {
                     $this->config['filter'] = strtr($this->config['filter'], $replaces);
                 }
 
                 foreach (array('base_dn', 'filter', 'member_filter') as $k) {
                     if (!empty($this->config['groups'][$k])) {
                         $this->config['groups'][$k] = strtr($this->config['groups'][$k], $replaces);
                     }
                 }
 
                 if (empty($bind_user)) {
                     $bind_user = $u;
                 }
             }
 
             if (empty($bind_pass)) {
                 $this->ready = true;
             }
             else {
                 if (!empty($this->config['auth_cid'])) {
                     $this->ready = $this->sasl_bind($this->config['auth_cid'], $bind_pass, $bind_dn);
                 }
                 else if (!empty($bind_dn)) {
                     $this->ready = $this->bind($bind_dn, $bind_pass);
                 }
                 else {
                     $this->ready = $this->sasl_bind($bind_user, $bind_pass);
                 }
             }
 
             // connection established, we're done here
             if ($this->ready) {
                 break;
             }
 
         }  // end foreach hosts
 
         if (!is_resource($this->conn)) {
             rcube::raise_error(array('code' => 100, 'type' => 'ldap',
                 'file' => __FILE__, 'line' => __LINE__,
                 'message' => "Could not connect to any LDAP server, last tried $host"), true);
 
             $this->ready = false;
         }
 
         return $this->ready;
     }
 
     /**
      * Fetches user data from LDAP addressbook
      */
     function get_user_record($user, $host)
     {
         $rcmail  = rcube::get_instance();
         $filter  = $rcmail->config->get('kolab_auth_filter');
         $filter  = $this->parse_vars($filter, $user, $host);
         $base_dn = $this->parse_vars($this->config['base_dn'], $user, $host);
         $scope   = $this->config['scope'];
 
         // @TODO: print error if filter is empty
 
         // get record
         if ($result = parent::search($base_dn, $filter, $scope, $this->attributes)) {
             if ($result->count() == 1) {
                 $entries = $result->entries(true);
                 $dn      = key($entries);
                 $entry   = array_pop($entries);
                 $entry   = $this->field_mapping($dn, $entry);
 
                 return $entry;
             }
         }
     }
 
     /**
      * Fetches user data from LDAP addressbook
      */
     function get_user_groups($dn, $user, $host)
     {
         if (empty($dn) || empty($this->config['groups'])) {
             return array();
         }
 
         $base_dn     = $this->parse_vars($this->config['groups']['base_dn'], $user, $host);
         $name_attr   = $this->config['groups']['name_attr'] ? $this->config['groups']['name_attr'] : 'cn';
         $member_attr = $this->get_group_member_attr();
         $filter      = "(member=$dn)(uniqueMember=$dn)";
 
         if ($member_attr != 'member' && $member_attr != 'uniqueMember')
             $filter .= "($member_attr=$dn)";
         $filter = strtr("(|$filter)", array("\\" => "\\\\"));
 
         $result = parent::search($base_dn, $filter, 'sub', array('dn', $name_attr));
 
         if (!$result) {
             return array();
         }
 
         $groups = array();
         foreach ($result as $entry) {
             $dn    = $entry['dn'];
             $entry = rcube_ldap_generic::normalize_entry($entry);
 
             $groups[$dn] = $entry[$name_attr];
         }
 
         return $groups;
     }
 
     /**
      * Get a specific LDAP record
      *
      * @param string DN
      *
      * @return array Record data
      */
     function get_record($dn)
     {
         if (!$this->ready) {
             return;
         }
 
         if ($rec = $this->get_entry($dn, $this->attributes)) {
             $rec = rcube_ldap_generic::normalize_entry($rec);
             $rec = $this->field_mapping($dn, $rec);
         }
 
         return $rec;
     }
 
     /**
      * Replace LDAP record data items
      *
      * @param string $dn    DN
      * @param array  $entry LDAP entry
      *
      * return bool True on success, False on failure
      */
     function replace($dn, $entry)
     {
         // fields mapping
         foreach ($this->fieldmap as $field => $attr) {
             if (array_key_exists($field, $entry)) {
                 $entry[$attr] = $entry[$field];
                 if ($attr != $field) {
                     unset($entry[$field]);
                 }
             }
         }
 
         return $this->mod_replace($dn, $entry);
     }
 
     /**
      * Search records (simplified version of rcube_ldap::search)
      *
      * @param mixed   $fields   The field name or array of field names to search in
      * @param string  $value    Search value
      * @param int     $mode     Matching mode:
      *                          0 - partial (*abc*),
      *                          1 - strict (=),
      *                          2 - prefix (abc*)
      * @param array   $required List of fields that cannot be empty
      * @param int     $limit    Number of records
      * @param int     $count    Returns the number of records found
      *
      * @return array List of LDAP records found
      */
     function dosearch($fields, $value, $mode=1, $required = array(), $limit = 0, &$count = 0)
     {
         if (empty($fields)) {
             return array();
         }
 
         $mode  = intval($mode);
 
         // try to resolve field names into ldap attributes
         $fieldmap = $this->fieldmap;
         $attrs = array_map(function($f) use ($fieldmap) {
             return array_key_exists($f, $fieldmap) ? $fieldmap[$f] : $f;
         }, (array)$fields);
 
         // compose a full-text-search-like filter
         if (count($attrs) > 1 || $mode != 1) {
             $filter = self::fulltext_search_filter($value, $attrs, $mode);
         }
         // direct search
         else {
             $field  = $attrs[0];
             $filter = "($field=" . self::quote_string($value) . ")";
         }
 
         // add required (non empty) fields filter
         $req_filter = '';
 
         foreach ((array)$required as $field) {
             $attr = array_key_exists($field, $this->fieldmap) ? $this->fieldmap[$field] : $field;
 
             // only add if required field is not already in search filter
             if (!in_array($attr, $attrs)) {
                 $req_filter .= "($attr=*)";
             }
         }
 
         if (!empty($req_filter)) {
             $filter = '(&' . $req_filter . $filter . ')';
         }
 
         // avoid double-wildcard if $value is empty
         $filter = preg_replace('/\*+/', '*', $filter);
 
         // add general filter to query
         if (!empty($this->config['filter'])) {
             $filter = '(&(' . preg_replace('/^\(|\)$/', '', $this->config['filter']) . ')' . $filter . ')';
         }
 
         $base_dn = $this->parse_vars($this->config['base_dn']);
         $scope   = $this->config['scope'];
         $attrs   = array_values($this->fieldmap);
         $list    = array();
 
         if ($result = $this->search($base_dn, $filter, $scope, $attrs)) {
             $count = $result->count();
             $i = 0;
             foreach ($result as $entry) {
                 if ($limit && $limit <= $i) {
                     break;
                 }
 
                 $dn        = $entry['dn'];
                 $entry     = rcube_ldap_generic::normalize_entry($entry);
                 $list[$dn] = $this->field_mapping($dn, $entry);
                 $i++;
             }
         }
 
         return $list;
     }
 
     /**
      * Set filter used in search()
      */
     function set_filter($filter)
     {
         $this->config['filter'] = $filter;
     }
 
     /**
      * Maps LDAP attributes to defined fields
      */
     protected function field_mapping($dn, $entry)
     {
         $entry['dn'] = $dn;
 
         // fields mapping
         foreach ($this->fieldmap as $field => $attr) {
             // $entry might be indexed by lower-case attribute names
             $attr_lc = strtolower($attr);
             if (isset($entry[$attr_lc])) {
                 $entry[$field] = $entry[$attr_lc];
             }
             else if (isset($entry[$attr])) {
                 $entry[$field] = $entry[$attr];
             }
         }
 
         // compose display name according to config
         if (empty($this->fieldmap['displayname'])) {
             $entry['displayname'] = rcube_addressbook::compose_search_name(
                 $entry,
                 $entry['email'],
                 $entry['name'],
                 $this->conf['kolab_auth_user_displayname']
             );
         }
 
         return $entry;
     }
 
     /**
      * Detects group member attribute name
      */
     private function get_group_member_attr($object_classes = array())
     {
         if (empty($object_classes)) {
             $object_classes = $this->config['groups']['object_classes'];
         }
         if (!empty($object_classes)) {
             foreach ((array)$object_classes as $oc) {
                 switch (strtolower($oc)) {
                     case 'group':
                     case 'groupofnames':
                     case 'kolabgroupofnames':
                         $member_attr = 'member';
                         break;
 
                     case 'groupofuniquenames':
                     case 'kolabgroupofuniquenames':
                         $member_attr = 'uniqueMember';
                         break;
                 }
             }
         }
 
         if (!empty($member_attr)) {
             return $member_attr;
         }
 
         if (!empty($this->config['groups']['member_attr'])) {
             return $this->config['groups']['member_attr'];
         }
 
         return 'member';
     }
 
     /**
      * Prepares filter query for LDAP search
      */
     function parse_vars($str, $user = null, $host = null)
     {
         // When authenticating user $user is always set
         // if not set it means we use this LDAP object for other
         // purposes, e.g. kolab_delegation, then username with
         // correct domain is in a session
         if (!$user) {
             $user = $_SESSION['username'];
         }
 
         $dc = null;
         if (isset($this->icache[$user])) {
             list($user, $dc) = $this->icache[$user];
         }
         else {
             $orig_user = $user;
             $rcmail = rcube::get_instance();
 
             // get default domain
             if ($username_domain = $rcmail->config->get('username_domain')) {
                 if ($host && is_array($username_domain) && isset($username_domain[$host])) {
                     $domain = rcube_utils::parse_host($username_domain[$host], $host);
                 }
                 else if (is_string($username_domain)) {
                     $domain = rcube_utils::parse_host($username_domain, $host);
                 }
             }
 
             // realmed username (with domain)
             if (strpos($user, '@')) {
                 list($usr, $dom) = explode('@', $user);
 
                 // unrealm domain, user login can contain a domain alias
                 if ($dom != $domain && ($dc = $this->domain_root_dn($dom))) {
                     // @FIXME: we should replace domain in $user, I suppose
                 }
             }
             else if ($domain) {
                 $user .= '@' . $domain;
             }
 
             $this->icache[$orig_user] = array($user, $dc);
         }
 
         // replace variables in filter
         list($u, $d) = explode('@', $user);
 
         // hierarchal domain string
         if (empty($dc)) {
             $dc = 'dc=' . strtr($d, array('.' => ',dc='));
         }
 
         $replaces = array('%dc' => $dc, '%d' => $d, '%fu' => $user, '%u' => $u);
 
         $this->parse_replaces = $replaces;
 
         return strtr($str, $replaces);
     }
 
     /**
      * Returns variables used for replacement in (last) parse_vars() call
      *
      * @return array Variable-value hash array
      */
     public function get_parse_vars()
     {
         return $this->parse_replaces;
     }
 
     /**
      * Register additional fields
      */
     public function extend_fieldmap($map)
     {
         foreach ((array)$map as $name => $attr) {
             if (!in_array($attr, $this->attributes)) {
                 $this->attributes[]    = $attr;
                 $this->fieldmap[$name] = $attr;
             }
         }
     }
 
     /**
      * HTML-safe DN string encoding
      *
      * @param string $str DN string
      *
      * @return string Encoded HTML identifier string
      */
     static function dn_encode($str)
     {
         return rtrim(strtr(base64_encode($str), '+/', '-_'), '=');
     }
 
     /**
      * Decodes DN string encoded with _dn_encode()
      *
      * @param string $str Encoded HTML identifier string
      *
      * @return string DN string
      */
     static function dn_decode($str)
     {
         $str = str_pad(strtr($str, '-_', '+/'), strlen($str) % 4, '=', STR_PAD_RIGHT);
         return base64_decode($str);
     }
 }