From bffca14d964091b3256868bc42bcb9417a72629b Mon Sep 17 00:00:00 2001 From: Aleksander Machniak <alec@alec.pl> Date: Tue, 19 Jan 2016 03:37:29 -0500 Subject: [PATCH] Exit when imagecreatetruecolor() fails --- program/lib/Roundcube/rcube_ldap.php | 483 ++++++++++++++++++++++++++++++++++------------------- 1 files changed, 309 insertions(+), 174 deletions(-) diff --git a/program/lib/Roundcube/rcube_ldap.php b/program/lib/Roundcube/rcube_ldap.php index 522c81d..b08ee7b 100644 --- a/program/lib/Roundcube/rcube_ldap.php +++ b/program/lib/Roundcube/rcube_ldap.php @@ -1,6 +1,6 @@ <?php -/* +/** +-----------------------------------------------------------------------+ | This file is part of the Roundcube Webmail client | | Copyright (C) 2006-2013, The Roundcube Dev Team | @@ -27,16 +27,18 @@ */ class rcube_ldap extends rcube_addressbook { - /** public properties */ + // public properties public $primary_key = 'ID'; public $groups = false; public $readonly = true; public $ready = false; public $group_id = 0; public $coltypes = array(); + public $export_groups = false; - /** private properties */ + // private properties protected $ldap; + protected $formats = array(); protected $prop = array(); protected $fieldmap = array(); protected $filter = ''; @@ -44,11 +46,27 @@ protected $result; protected $ldap_result; protected $mail_domain = ''; - protected $debug = false; + protected $debug = false; + + /** + * Group objectclass (lowercase) to member attribute mapping + * + * @var array + */ + private $group_types = array( + 'group' => 'member', + 'groupofnames' => 'member', + 'kolabgroupofnames' => 'member', + 'groupofuniquenames' => 'uniqueMember', + 'kolabgroupofuniquenames' => 'uniqueMember', + 'univentiongroup' => 'uniqueMember', + 'groupofurls' => null, + ); private $base_dn = ''; private $groups_base_dn = ''; - private $group_url; + private $group_data; + private $group_search_cache; private $cache; @@ -78,12 +96,15 @@ $this->prop['groups']['name_attr'] = 'cn'; if (empty($this->prop['groups']['scope'])) $this->prop['groups']['scope'] = 'sub'; + // extend group objectclass => member attribute mapping + if (!empty($this->prop['groups']['class_member_attr'])) + $this->group_types = array_merge($this->group_types, $this->prop['groups']['class_member_attr']); // add group name attrib to the list of attributes to be fetched $fetch_attributes[] = $this->prop['groups']['name_attr']; } - if (is_array($p['group_filters']) && count($p['group_filters'])) { - $this->groups = true; + if (is_array($p['group_filters'])) { + $this->groups = $this->groups || count($p['group_filters']); foreach ($p['group_filters'] as $k => $group_filter) { // set default name attribute to cn @@ -97,23 +118,40 @@ // fieldmap property is given if (is_array($p['fieldmap'])) { + $p['fieldmap'] = array_filter($p['fieldmap']); foreach ($p['fieldmap'] as $rf => $lf) - $this->fieldmap[$rf] = $this->_attr_name(strtolower($lf)); + $this->fieldmap[$rf] = $this->_attr_name($lf); } else if (!empty($p)) { // read deprecated *_field properties to remain backwards compatible foreach ($p as $prop => $value) - if (preg_match('/^(.+)_field$/', $prop, $matches)) - $this->fieldmap[$matches[1]] = $this->_attr_name(strtolower($value)); + if (!empty($value) && preg_match('/^(.+)_field$/', $prop, $matches)) + $this->fieldmap[$matches[1]] = $this->_attr_name($value); } // use fieldmap to advertise supported coltypes to the application foreach ($this->fieldmap as $colv => $lfv) { list($col, $type) = explode(':', $colv); - list($lf, $limit, $delim) = explode(':', $lfv); + $params = explode(':', $lfv); - if ($limit == '*') $limit = null; - else $limit = max(1, intval($limit)); + $lf = array_shift($params); + $limit = 1; + + foreach ($params as $idx => $param) { + // field format specification + if (preg_match('/^(date)\[(.+)\]$/i', $param, $m)) { + $this->formats[$lf] = array('type' => strtolower($m[1]), 'format' => $m[2]); + } + // first argument is a limit + else if ($idx === 0) { + if ($param == '*') $limit = null; + else $limit = max(1, intval($param)); + } + // second is a composite field separator + else if ($idx === 1 && $param) { + $this->coltypes[$col]['serialized'][$type] = $param; + } + } if (!is_array($this->coltypes[$col])) { $subtypes = $type ? array($type) : null; @@ -125,10 +163,7 @@ $this->coltypes[$col]['limit'] += $limit; } - if ($delim) - $this->coltypes[$col]['serialized'][$type] = $delim; - - $this->fieldmap[$colv] = $lf; + $this->fieldmap[$colv] = $lf; } // support for composite address @@ -174,7 +209,7 @@ } foreach ($this->prop['required_fields'] as $key => $val) { - $this->prop['required_fields'][$key] = $this->_attr_name(strtolower($val)); + $this->prop['required_fields'][$key] = $this->_attr_name($val); } // Build sub_fields filter @@ -213,16 +248,14 @@ // initialize ldap wrapper object $this->ldap = new rcube_ldap_generic($this->prop); - $this->ldap->set_cache($this->cache); - $this->ldap->set_debug($this->debug); + $this->ldap->config_set(array('cache' => $this->cache, 'debug' => $this->debug)); $this->_connect(); } - /** - * Establish a connection to the LDAP server - */ + * Establish a connection to the LDAP server + */ private function _connect() { $rcube = rcube::get_instance(); @@ -252,8 +285,7 @@ $bind_dn = $this->prop['bind_dn']; $this->base_dn = $this->prop['base_dn']; - $this->groups_base_dn = ($this->prop['groups']['base_dn']) ? - $this->prop['groups']['base_dn'] : $this->base_dn; + $this->groups_base_dn = $this->prop['groups']['base_dn'] ?: $this->base_dn; // User specific access, generate the proper values to use. if ($this->prop['user_specific']) { @@ -263,17 +295,36 @@ } // Get the pieces needed for variable replacement. - if ($fu = $rcube->get_user_email()) + if ($fu = $rcube->get_user_email()) { list($u, $d) = explode('@', $fu); - else + } + else { $d = $this->mail_domain; + } $dc = 'dc='.strtr($d, array('.' => ',dc=')); // hierarchal domain string + + // resolve $dc through LDAP + if (!empty($this->prop['domain_filter']) && !empty($this->prop['search_bind_dn']) && + method_exists($this->ldap, 'domain_root_dn')) { + $this->ldap->bind($this->prop['search_bind_dn'], $this->prop['search_bind_pw']); + $dc = $this->ldap->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->prop['search_base_dn'] && $this->prop['search_filter']) { + if ($this->prop['search_base_dn'] && $this->prop['search_filter'] + && (strstr($bind_dn, '%dn') || strstr($this->base_dn, '%dn') || strstr($this->groups_base_dn, '%dn')) + ) { + $search_attribs = array('uid'); + if ($search_bind_attrib = (array)$this->prop['search_bind_attrib']) { + foreach ($search_bind_attrib as $r => $attr) { + $search_attribs[] = $attr; + $replaces[$r] = ''; + } + } + $search_bind_dn = strtr($this->prop['search_bind_dn'], $replaces); $search_base_dn = strtr($this->prop['search_base_dn'], $replaces); $search_filter = strtr($this->prop['search_filter'], $replaces); @@ -291,8 +342,7 @@ // we need to use a separate LDAP connection if (!empty($this->prop['vlv'])) { $ldap = new rcube_ldap_generic($this->prop); - $ldap->set_debug($this->debug); - $ldap->set_cache($this->cache); + $ldap->config_set(array('cache' => $this->cache, 'debug' => $this->debug)); if (!$ldap->connect($host)) { continue; } @@ -303,10 +353,18 @@ } } - $res = $ldap->search($search_base_dn, $search_filter, 'sub', array('uid')); + $res = $ldap->search($search_base_dn, $search_filter, 'sub', $search_attribs); if ($res) { $res->rewind(); - $replaces['%dn'] = $res->get_dn(); + $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) { @@ -336,6 +394,24 @@ $bind_dn = strtr($bind_dn, $replaces); $this->base_dn = strtr($this->base_dn, $replaces); $this->groups_base_dn = strtr($this->groups_base_dn, $replaces); + + // replace placeholders in filter settings + if (!empty($this->prop['filter'])) + $this->prop['filter'] = strtr($this->prop['filter'], $replaces); + + foreach (array('base_dn','filter','member_filter') as $k) { + if (!empty($this->prop['groups'][$k])) + $this->prop['groups'][$k] = strtr($this->prop['groups'][$k], $replaces); + } + + if (is_array($this->prop['group_filters'])) { + foreach ($this->prop['group_filters'] as $i => $gf) { + if (!empty($gf['base_dn'])) + $this->prop['group_filters'][$i]['base_dn'] = strtr($gf['base_dn'], $replaces); + if (!empty($gf['filter'])) + $this->prop['group_filters'][$i]['filter'] = strtr($gf['filter'], $replaces); + } + } if (empty($bind_user)) { $bind_user = $u; @@ -375,7 +451,6 @@ return $this->ready; } - /** * Close connection to LDAP server */ @@ -386,7 +461,6 @@ } } - /** * Returns address book name * @@ -396,7 +470,6 @@ { return $this->prop['name']; } - /** * Set internal list page @@ -420,7 +493,6 @@ $this->ldap->set_vlv_page($this->list_page, $this->page_size); } - /** * Set internal sort settings * @@ -433,7 +505,6 @@ $this->sort_col = $this->coltypes[$sort_col]['attributes'][0]; } - /** * Save a search string for future listings * @@ -443,7 +514,6 @@ { $this->filter = $filter; } - /** * Getter for saved search properties @@ -455,7 +525,6 @@ return $this->filter; } - /** * Reset all saved results and search parameters */ @@ -466,14 +535,13 @@ $this->filter = ''; } - /** * List the current set of contact records * - * @param array List of cols to show - * @param int Only return this number of records + * @param array List of cols to show + * @param int Only return this number of records * - * @return array Indexed list of contact records, each a hash array + * @return array Indexed list of contact records, each a hash array */ function list_records($cols=null, $subset=0) { @@ -499,7 +567,8 @@ $this->result = new rcube_result_set($entries['count'], ($this->list_page-1) * $this->page_size); } else { - $prop = $this->group_id ? $this->group_data : $this->prop; + $prop = $this->group_id ? $this->group_data : $this->prop; + $base_dn = $this->group_id ? $prop['base_dn'] : $this->base_dn; // use global search filter if (!empty($this->filter)) @@ -507,7 +576,7 @@ // exec LDAP search if no result resource is stored if ($this->ready && !$this->ldap_result) - $this->ldap_result = $this->ldap->search($prop['base_dn'], $prop['filter'], $prop['scope'], $this->prop['attributes'], $prop); + $this->ldap_result = $this->ldap->search($base_dn, $prop['filter'], $prop['scope'], $this->prop['attributes'], $prop); // count contacts for this user $this->result = $this->count(); @@ -540,9 +609,10 @@ /** * Get all members of the given group * - * @param string Group DN - * @param array Group entries (if called recursively) - * @return array Accumulated group members + * @param string Group DN + * @param boolean Count only + * @param array Group entries (if called recursively) + * @return array Accumulated group members */ function list_group_members($dn, $count = false, $entries = null) { @@ -550,7 +620,7 @@ // fetch group object if (empty($entries)) { - $attribs = array('dn','objectClass','member','uniqueMember','memberURL'); + $attribs = array_merge(array('dn','objectClass','memberURL'), array_values($this->group_types)); $entries = $this->ldap->read_entries($dn, '(objectClass=*)', $attribs); if ($entries === false) { return $group_members; @@ -562,16 +632,16 @@ $attrs = array(); foreach ((array)$entry['objectclass'] as $objectclass) { - if (strtolower($objectclass) == 'groupofurls') { - $members = $this->_list_group_memberurl($dn, $entry, $count); - $group_members = array_merge($group_members, $members); - } - else if (($member_attr = $this->get_group_member_attr(array($objectclass), '')) + if (($member_attr = $this->get_group_member_attr(array($objectclass), '')) && ($member_attr = strtolower($member_attr)) && !in_array($member_attr, $attrs) ) { $members = $this->_list_group_members($dn, $entry, $member_attr, $count); $group_members = array_merge($group_members, $members); $attrs[] = $member_attr; + } + else if (!empty($entry['memberurl'])) { + $members = $this->_list_group_memberurl($dn, $entry, $count); + $group_members = array_merge($group_members, $members); } if ($this->prop['sizelimit'] && count($group_members) > $this->prop['sizelimit']) { @@ -589,6 +659,7 @@ * @param string Group DN * @param array Group entry * @param string Member attribute to use + * @param boolean Count only * @return array Accumulated group members */ private function _list_group_members($dn, $entry, $attr, $count) @@ -602,11 +673,10 @@ // read these attributes for all members $attrib = $count ? array('dn','objectClass') : $this->prop['list_attributes']; - $attrib[] = 'member'; - $attrib[] = 'uniqueMember'; + $attrib = array_merge($attrib, array_values($this->group_types)); $attrib[] = 'memberURL'; - $filter = $this->prop['groups']['member_filter'] ? $this->prop['groups']['member_filter'] : '(objectclass=*)'; + $filter = $this->prop['groups']['member_filter'] ?: '(objectclass=*)'; for ($i=0; $i < $entry[$attr]['count']; $i++) { if (empty($entry[$attr][$i])) @@ -641,8 +711,9 @@ for ($i=0; $i < $entry['memberurl']['count']; $i++) { // extract components from url - if (!preg_match('!ldap:///([^\?]+)\?\?(\w+)\?(.*)$!', $entry['memberurl'][$i], $m)) + if (!preg_match('!ldap://[^/]*/([^\?]+)\?\?(\w+)\?(.*)$!', $entry['memberurl'][$i], $m)) { continue; + } // add search filter if any $filter = $this->filter ? '(&(' . $m[3] . ')(' . $this->filter . '))' : $m[3]; @@ -650,7 +721,7 @@ if ($result = $this->ldap->search($m[1], $filter, $m[2], $attrs, $this->group_data)) { $entries = $result->entries(); for ($j = 0; $j < $entries['count']; $j++) { - if (self::is_group_entry($entries[$j]) && ($nested_group_members = $this->list_group_members($entries[$j]['dn'], $count))) + if ($this->is_group_entry($entries[$j]) && ($nested_group_members = $this->list_group_members($entries[$j]['dn'], $count))) $group_members = array_merge($group_members, $nested_group_members); else $group_members[] = $entries[$j]; @@ -668,7 +739,6 @@ { return strcmp($a[$this->sort_col][0], $b[$this->sort_col][0]); } - /** * Search contacts @@ -709,16 +779,16 @@ if ($this->prop['vlv_search'] && $this->ready && join(',', (array)$fields) == join(',', $list_fields)) { $this->result = new rcube_result_set(0); - $search_suffix = $this->prop['fuzzy_search'] && $mode != 1 ? '*' : ''; + $this->ldap->config_set('fuzzy_search', intval($this->prop['fuzzy_search'] && $mode != 1)); $ldap_data = $this->ldap->search($this->base_dn, $this->prop['filter'], $this->prop['scope'], $this->prop['attributes'], - array('search' => $value . $search_suffix /*, 'sort' => $this->prop['sort'] */)); + array('search' => $value /*, 'sort' => $this->prop['sort'] */)); if ($ldap_data === false) { return $this->result; } // get all entries of this page and post-filter those that really match the query $search = mb_strtolower($value); - foreach ($ldap_data as $i => $entry) { + foreach ($ldap_data as $entry) { $rec = $this->_ldap2result($entry); foreach ($fields as $f) { foreach ((array)$rec[$f] as $val) { @@ -734,33 +804,24 @@ return $this->result; } - // use AND operator for advanced searches - $filter = is_array($value) ? '(&' : '(|'; - // set wildcards - $wp = $ws = ''; - if (!empty($this->prop['fuzzy_search']) && $mode != 1) { - $ws = '*'; - if (!$mode) { - $wp = '*'; - } - } + // advanced per-attribute search + if (is_array($value)) { + // use AND operator for advanced searches + $filter = '(&'; - if ($fields == '*') { - // search_fields are required for fulltext search - if (empty($this->prop['search_fields'])) { - $this->set_error(self::ERROR_SEARCH, 'nofulltextsearch'); - $this->result = new rcube_result_set(); - return $this->result; - } - if (is_array($this->prop['search_fields'])) { - foreach ($this->prop['search_fields'] as $field) { - $filter .= "($field=$wp" . rcube_ldap_generic::quote_string($value) . "$ws)"; + // set wildcards + $wp = $ws = ''; + if (!empty($this->prop['fuzzy_search']) && $mode != 1) { + $ws = '*'; + if (!$mode) { + $wp = '*'; } } - } - else { + foreach ((array)$fields as $idx => $field) { - $val = is_array($value) ? $value[$idx] : $value; + $val = $value[$idx]; + if (!strlen($val)) + continue; if ($attrs = $this->_map_field($field)) { if (count($attrs) > 1) $filter .= '(|'; @@ -770,8 +831,32 @@ $filter .= ')'; } } + + $filter .= ')'; } - $filter .= ')'; + else { + if ($fields == '*') { + // search_fields are required for fulltext search + if (empty($this->prop['search_fields'])) { + $this->set_error(self::ERROR_SEARCH, 'nofulltextsearch'); + $this->result = new rcube_result_set(); + return $this->result; + } + $attributes = (array)$this->prop['search_fields']; + } + else { + // map address book fields into ldap attributes + $attributes = array(); + foreach ((array) $fields as $field) { + if ($this->coltypes[$field] && ($attrs = $this->coltypes[$field]['attributes'])) { + $attributes = array_merge($attributes, (array) $attrs); + } + } + } + + // compose a full-text-like search filter + $filter = rcube_ldap_generic::fulltext_search_filter($value, $attributes, $mode); + } // add required (non empty) fields filter $req_filter = ''; @@ -809,7 +894,6 @@ return $this->result; } - /** * Count number of available contacts in database * @@ -826,18 +910,17 @@ } // We have a connection but no result set, attempt to get one. else if ($this->ready) { - $prop = $this->group_id ? $this->group_data : $this->prop; + $prop = $this->group_id ? $this->group_data : $this->prop; + $base_dn = $this->group_id ? $this->group_base_dn : $this->base_dn; if (!empty($this->filter)) { // Use global search filter $prop['filter'] = $this->filter; } - - $count = $this->ldap->search($prop['base_dn'], $prop['filter'], $prop['scope'], array('dn'), $prop, true); + $count = $this->ldap->search($base_dn, $prop['filter'], $prop['scope'], array('dn'), $prop, true); } return new rcube_result_set($count, ($this->list_page-1) * $this->page_size); } - /** * Return the last result set @@ -848,7 +931,6 @@ { return $this->result; } - /** * Get a specific contact record @@ -891,6 +973,22 @@ return $assoc ? $res : $this->result; } + /** + * Returns the last error occurred (e.g. when updating/inserting failed) + * + * @return array Hash array with the following fields: type, message + */ + function get_error() + { + $err = $this->error; + + // check ldap connection for errors + if (!$err && $this->ldap->get_error()) { + $err = array(self::ERROR_SEARCH, $this->ldap->get_error()); + } + + return $err; + } /** * Check the given data before saving. @@ -963,15 +1061,17 @@ return true; } - /** * Create a new contact record * - * @param array Hash array with save data + * @param array Associative array with save data + * Keys: Field name with optional section in the form FIELD:SECTION + * Values: Field value. Can be either a string or an array of strings for multiple values + * @param boolean True to check for duplicates first * - * @return encoded record ID on success, False on error + * @return mixed The created record ID on success, False on error */ - function insert($save_cols) + function insert($save_cols, $check = false) { // Map out the column names to their LDAP ones to build the new entry. $newentry = $this->_map_data($save_cols); @@ -1009,7 +1109,7 @@ } } - if (!$this->ldap->add($dn, $newentry)) { + if (!$this->ldap->add_entry($dn, $newentry)) { $this->set_error(self::ERROR_SAVING, 'errorsaving'); return false; } @@ -1021,7 +1121,7 @@ 'objectClass' => (array) $this->prop['sub_fields'][$xidx], ); - $this->ldap->add($xdn, $xf); + $this->ldap->add_entry($xdn, $xf); } $dn = self::dn_encode($dn); @@ -1032,7 +1132,6 @@ return $dn; } - /** * Update a specific contact record @@ -1164,7 +1263,7 @@ if (!empty($subdeldata)) { foreach ($subdeldata as $fld => $val) { $subdn = $fld.'='.rcube_ldap_generic::quote_string($val).','.$dn; - if (!$this->ldap->delete($subdn)) { + if (!$this->ldap->delete_entry($subdn)) { return false; } } @@ -1191,8 +1290,7 @@ // change the group membership of the contact if ($this->groups) { $group_ids = $this->get_record_groups($dn); - foreach ($group_ids as $group_id => $group_prop) - { + foreach (array_keys($group_ids) as $group_id) { $this->remove_from_group($group_id, $dn); $this->add_to_group($group_id, $newdn); } @@ -1209,13 +1307,12 @@ $fld => $val, 'objectClass' => (array) $this->prop['sub_fields'][$fld], ); - $this->ldap->add($subdn, $xf); + $this->ldap->add_entry($subdn, $xf); } } - return $newdn ? $newdn : true; + return $newdn ?: true; } - /** * Mark one or more contact records as deleted @@ -1239,7 +1336,7 @@ if ($this->sub_filter) { if ($entries = $this->ldap->list_entries($dn, $this->sub_filter)) { foreach ($entries as $entry) { - if (!$this->ldap->delete($entry['dn'])) { + if (!$this->ldap->delete_entry($entry['dn'])) { $this->set_error(self::ERROR_SAVING, 'errorsaving'); return false; } @@ -1248,16 +1345,16 @@ } // Delete the record. - if (!$this->ldap->delete($dn)) { + if (!$this->ldap->delete_entry($dn)) { $this->set_error(self::ERROR_SAVING, 'errorsaving'); return false; } - // remove contact from all groups where he was member + // remove contact from all groups where he was a member if ($this->groups) { $dn = self::dn_encode($dn); $group_ids = $this->get_record_groups($dn); - foreach ($group_ids as $group_id => $group_prop) { + foreach (array_keys($group_ids) as $group_id) { $this->remove_from_group($group_id, $dn); } } @@ -1266,20 +1363,31 @@ return count($ids); } - /** * Remove all contact records + * + * @param bool $with_groups Delete also groups if enabled */ - function delete_all() + function delete_all($with_groups = false) { // searching for contact entries - $dn_list = $this->ldap->list_entries($this->base_dn, $this->prop['filter'] ? $this->prop['filter'] : '(objectclass=*)'); + $dn_list = $this->ldap->list_entries($this->base_dn, $this->prop['filter'] ?: '(objectclass=*)'); if (!empty($dn_list)) { foreach ($dn_list as $idx => $entry) { $dn_list[$idx] = self::dn_encode($entry['dn']); } $this->delete($dn_list); + } + + if ($with_groups && $this->groups && ($groups = $this->_fetch_groups()) && count($groups)) { + foreach ($groups as $group) { + $this->ldap->delete_entry($group['dn']); + } + + if ($this->cache) { + $this->cache->remove('groups'); + } } } @@ -1323,7 +1431,6 @@ } } - /** * Converts LDAP entry into an array */ @@ -1336,10 +1443,20 @@ $out[$this->primary_key] = self::dn_encode($rec['dn']); // determine record type - if (self::is_group_entry($rec)) { + if ($this->is_group_entry($rec)) { $out['_type'] = 'group'; $out['readonly'] = true; - $fieldmap['name'] = $this->group_data['name_attr'] ? $this->group_data['name_attr'] : $this->prop['groups']['name_attr']; + $fieldmap['name'] = $this->group_data['name_attr'] ?: $this->prop['groups']['name_attr']; + } + + // assign object type from object class mapping + if (!empty($this->prop['class_type_map'])) { + foreach (array_map('strtolower', (array)$rec['objectclass']) as $objcls) { + if (!empty($this->prop['class_type_map'][$objcls])) { + $out['_type'] = $this->prop['class_type_map'][$objcls]; + break; + } + } } foreach ($fieldmap as $rf => $lf) @@ -1354,7 +1471,7 @@ if ($col == 'email' && $this->mail_domain && !strpos($value, '@')) $out[$rf][] = sprintf('%s@%s', $value, $this->mail_domain); else if (in_array($col, array('street','zipcode','locality','country','region'))) - $out['address'.($subtype?':':'').$subtype][$i][$col] = $value; + $out['address' . ($subtype ? ':' : '') . $subtype][$i][$col] = $value; else if ($col == 'address' && strpos($value, '$') !== false) // address data is represented as string separated with $ list($out[$rf][$i]['street'], $out[$rf][$i]['locality'], $out[$rf][$i]['zipcode'], $out[$rf][$i]['country']) = explode('$', $value); else if ($rec[$lf]['count'] > 1) @@ -1372,7 +1489,6 @@ return $out; } - /** * Return LDAP attribute(s) for the given field */ @@ -1380,7 +1496,6 @@ { return (array)$this->coltypes[$field]['attributes']; } - /** * Convert a record data set into LDAP field attributes @@ -1434,9 +1549,22 @@ } } + foreach ($this->formats as $fld => $format) { + if (empty($ldap_data[$fld])) { + continue; + } + + switch ($format['type']) { + case 'date': + if ($dt = rcube_utils::anytodatetime($ldap_data[$fld])) { + $ldap_data[$fld] = $dt->format($format['format']); + } + break; + } + } + return $ldap_data; } - /** * Returns unified attribute name (resolving aliases) @@ -1445,15 +1573,16 @@ { // list of known attribute aliases static $aliases = array( - 'gn' => 'givenname', + 'gn' => 'givenname', 'rfc822mailbox' => 'email', - 'userid' => 'uid', - 'emailaddress' => 'email', - 'pkcs9email' => 'email', + 'userid' => 'uid', + 'emailaddress' => 'email', + 'pkcs9email' => 'email', ); list($name, $limit) = explode(':', $namev, 2); $suffix = $limit ? ':'.$limit : ''; + $name = strtolower($name); return (isset($aliases[$name]) ? $aliases[$name] : $name) . $suffix; } @@ -1461,25 +1590,12 @@ /** * Determines whether the given LDAP entry is a group record */ - private static function is_group_entry($entry) + private function is_group_entry($entry) { - return array_intersect( - array('group', 'groupofnames', 'kolabgroupofnames', 'groupofuniquenames', - 'kolabgroupofuniquenames', 'groupofurls', 'univentiongroup'), - array_map('strtolower', (array)$entry['objectclass']) - ); - } + $classes = array_map('strtolower', (array)$entry['objectclass']); - /** - * Prints debug info to the log - */ - private function _debug($str) - { - if ($this->debug) { - rcube::write_log('ldap', $str); - } + return count(array_intersect(array_keys($this->group_types), $classes)) > 0; } - /** * Activate/deactivate debug mode @@ -1491,10 +1607,9 @@ $this->debug = $dbg; if ($this->ldap) { - $this->ldap->set_debug($dbg); + $this->ldap->config_set('debug', $dbg); } } - /** * Setter for the current group @@ -1528,12 +1643,12 @@ return array(); } - $group_cache = $this->_fetch_groups(); + $group_cache = $this->_fetch_groups($search, $mode); $groups = array(); if ($search) { foreach ($group_cache as $group) { - if ($this->compare_search_value('name', $group['name'], $search, $mode)) { + if ($this->compare_search_value('name', $group['name'], mb_strtolower($search), $mode)) { $groups[] = $group; } } @@ -1548,16 +1663,26 @@ /** * Fetch groups from server */ - private function _fetch_groups($vlv_page = null) + private function _fetch_groups($search = null, $mode = 0, $vlv_page = null) { + // reset group search cache + if ($search !== null && $vlv_page === null) { + $this->group_search_cache = null; + } + // return in-memory cache from previous search results + else if (is_array($this->group_search_cache) && $vlv_page === null) { + return $this->group_search_cache; + } + // special case: list groups from 'group_filters' config - if ($vlv_page === null && !empty($this->prop['group_filters'])) { + if ($vlv_page === null && $search === null && is_array($this->prop['group_filters'])) { $groups = array(); + $rcube = rcube::get_instance(); // list regular groups configuration as special filter if (!empty($this->prop['groups']['filter'])) { $id = '__groups__'; - $groups[$id] = array('ID' => $id, 'name' => rcube_label('groups'), 'virtual' => true) + $this->prop['groups']; + $groups[$id] = array('ID' => $id, 'name' => $rcube->gettext('groups'), 'virtual' => true) + $this->prop['groups']; } foreach ($this->prop['group_filters'] as $id => $prop) { @@ -1567,14 +1692,15 @@ return $groups; } - if ($this->cache && $vlv_page === null && ($groups = $this->cache->get('groups')) !== null) { + if ($this->cache && $search === null && $vlv_page === null && ($groups = $this->cache->get('groups')) !== null) { return $groups; } $base_dn = $this->groups_base_dn; $filter = $this->prop['groups']['filter']; + $scope = $this->prop['groups']['scope']; $name_attr = $this->prop['groups']['name_attr']; - $email_attr = $this->prop['groups']['email_attr'] ? $this->prop['groups']['email_attr'] : 'mail'; + $email_attr = $this->prop['groups']['email_attr'] ?: 'mail'; $sort_attrs = $this->prop['groups']['sort'] ? (array)$this->prop['groups']['sort'] : array($name_attr); $sort_attr = $sort_attrs[0]; @@ -1588,12 +1714,26 @@ } $ldap = clone $this->ldap; - $ldap->set_config($this->prop['groups']); + $ldap->config_set($this->prop['groups']); $ldap->set_vlv_page($vlv_page+1, $page_size); } - $attrs = array_unique(array('dn', 'objectClass', $name_attr, $email_attr, $sort_attr)); - $ldap_data = $ldap->search($base_dn, $filter, $this->prop['groups']['scope'], $attrs, $this->prop['groups']); + $props = array('sort' => $this->prop['groups']['sort']); + $attrs = array_unique(array('dn', 'objectClass', $name_attr, $email_attr, $sort_attr)); + + // add search filter + if ($search !== null) { + // set wildcards + $wp = $ws = ''; + if (!empty($this->prop['fuzzy_search']) && $mode != 1) { + $ws = '*'; + $wp = !$mode ? '*' : ''; + } + $filter = "(&$filter($name_attr=$wp" . rcube_ldap_generic::quote_string($search) . "$ws))"; + $props['search'] = $wp . $search . $ws; + } + + $ldap_data = $ldap->search($base_dn, $filter, $scope, $attrs, $props); if ($ldap_data === false) { return array(); @@ -1630,7 +1770,7 @@ // call recursively until we have fetched all groups while ($this->prop['groups']['vlv'] && $group_count == $page_size) { - $next_page = $this->_fetch_groups(++$vlv_page); + $next_page = $this->_fetch_groups($search, $mode, ++$vlv_page); $groups = array_merge($groups, $next_page); $group_count = count($next_page); } @@ -1641,8 +1781,11 @@ } // cache this - if ($this->cache) { + if ($this->cache && $search === null) { $this->cache->set('groups', $groups); + } + else if ($search !== null) { + $this->group_search_cache = $groups; } return $groups; @@ -1705,14 +1848,14 @@ $new_dn = 'cn=' . rcube_ldap_generic::quote_string($group_name, true) . ',' . $this->groups_base_dn; $new_gid = self::dn_encode($new_dn); $member_attr = $this->get_group_member_attr(); - $name_attr = $this->prop['groups']['name_attr'] ? $this->prop['groups']['name_attr'] : 'cn'; + $name_attr = $this->prop['groups']['name_attr'] ?: 'cn'; $new_entry = array( 'objectClass' => $this->prop['groups']['object_classes'], - $name_attr => $group_name, - $member_attr => '', + $name_attr => $group_name, + $member_attr => '', ); - if (!$this->ldap->add($new_dn, $new_entry)) { + if (!$this->ldap->add_entry($new_dn, $new_entry)) { $this->set_error(self::ERROR_SAVING, 'errorsaving'); return false; } @@ -1735,7 +1878,7 @@ $group_cache = $this->_fetch_groups(); $del_dn = $group_cache[$group_id]['dn']; - if (!$this->ldap->delete($del_dn)) { + if (!$this->ldap->delete_entry($del_dn)) { $this->set_error(self::ERROR_SAVING, 'errorsaving'); return false; } @@ -1861,15 +2004,16 @@ $base_dn = $this->groups_base_dn; $contact_dn = self::dn_decode($contact_id); - $name_attr = $this->prop['groups']['name_attr'] ? $this->prop['groups']['name_attr'] : 'cn'; + $name_attr = $this->prop['groups']['name_attr'] ?: 'cn'; $member_attr = $this->get_group_member_attr(); $add_filter = ''; + if ($member_attr != 'member' && $member_attr != 'uniqueMember') $add_filter = "($member_attr=$contact_dn)"; $filter = strtr("(|(member=$contact_dn)(uniqueMember=$contact_dn)$add_filter)", array('\\' => '\\\\')); $ldap_data = $this->ldap->search($base_dn, $filter, 'sub', array('dn', $name_attr)); - if ($res === false) { + if ($ldap_data === false) { return array(); } @@ -1879,8 +2023,9 @@ $entry['dn'] = $ldap_data->get_dn(); $group_name = $entry[$name_attr][0]; $group_id = self::dn_encode($entry['dn']); - $groups[$group_id] = array('ID' => $group_id, 'name' => $group_name, 'dn' => $entry['dn']); + $groups[$group_id] = $group_name; } + return $groups; } @@ -1895,16 +2040,8 @@ if (!empty($object_classes)) { foreach ((array)$object_classes as $oc) { - switch (strtolower($oc)) { - case 'group': - case 'groupofnames': - case 'kolabgroupofnames': - return 'member'; - - case 'groupofuniquenames': - case 'kolabgroupofuniquenames': - case 'univentiongroup': - return 'uniqueMember'; + if ($attr = $this->group_types[strtolower($oc)]) { + return $attr; } } } @@ -1915,7 +2052,6 @@ return $default; } - /** * HTML-safe DN string encoding @@ -1943,5 +2079,4 @@ $str = str_pad(strtr($str, '-_', '+/'), strlen($str) % 4, '=', STR_PAD_RIGHT); return base64_decode($str); } - } -- Gitblit v1.9.1