Searching in both contacts and groups when LDAP addressbook with group_filters option is used

Conflicts:

	CHANGELOG
	program/steps/addressbook/search.inc
pull/6833/head
Aleksander Machniak 9 years ago
parent e48f8945b3
commit ac592fd169

@ -1,7 +1,7 @@
CHANGELOG Roundcube Webmail CHANGELOG Roundcube Webmail
=========================== ===========================
- Searching in both contacts and groups when LDAP addressbook with group_filters option is used
- Fix message list multi-select/deselect issue (#5219) - Fix message list multi-select/deselect issue (#5219)
- Fix bug where contact search menu fields where always unchecked in Larry skin - Fix bug where contact search menu fields where always unchecked in Larry skin
- Fix XSS issue in href attribute on area tag (#5240) - Fix XSS issue in href attribute on area tag (#5240)

@ -1246,21 +1246,37 @@ function rcube_webmail()
break; break;
case 'pushgroup': case 'pushgroup':
// add group ID to stack // add group ID and current search to stack
this.env.address_group_stack.push(props.id); var group = {
id: props.id,
search_request: this.env.search_request,
page: this.env.current_page,
search: this.env.search_request && this.gui_objects.qsearchbox ? this.gui_objects.qsearchbox.value : null
};
this.env.address_group_stack.push(group);
if (obj && event) if (obj && event)
rcube_event.cancel(event); rcube_event.cancel(event);
case 'listgroup': case 'listgroup':
this.reset_qsearch(); this.reset_qsearch();
this.list_contacts(props.source, props.id); this.list_contacts(props.source, props.id, 1, group);
break; break;
case 'popgroup': case 'popgroup':
if (this.env.address_group_stack.length > 1) { if (this.env.address_group_stack.length) {
this.env.address_group_stack.pop(); var old = this.env.address_group_stack.pop();
this.reset_qsearch(); this.reset_qsearch();
this.list_contacts(props.source, this.env.address_group_stack[this.env.address_group_stack.length-1]);
if (old.search_request) {
// this code is executed when going back to the search result
if (old.search && this.gui_objects.qsearchbox)
$(this.gui_objects.qsearchbox).val(old.search);
this.env.search_request = old.search_request;
this.list_contacts_remote(null, null, this.env.current_page = old.page);
}
else
this.list_contacts(props.source, this.env.address_group_stack[this.env.address_group_stack.length-1].id);
} }
break; break;
@ -4846,9 +4862,9 @@ function rcube_webmail()
return false; return false;
}; };
this.list_contacts = function(src, group, page) this.list_contacts = function(src, group, page, search)
{ {
var win, folder, url = {}, var win, folder, index = -1, url = {},
refresh = src === undefined && group === undefined && page === undefined, refresh = src === undefined && group === undefined && page === undefined,
target = window; target = window;
@ -4858,9 +4874,6 @@ function rcube_webmail()
if (refresh) if (refresh)
group = this.env.group; group = this.env.group;
if (page && this.current_page == page && src == this.env.source && group == this.env.group)
return false;
if (src != this.env.source) { if (src != this.env.source) {
page = this.env.current_page = 1; page = this.env.current_page = 1;
this.reset_qsearch(); this.reset_qsearch();
@ -4877,21 +4890,26 @@ function rcube_webmail()
this.env.group = group; this.env.group = group;
// truncate groups listing stack // truncate groups listing stack
var index = $.inArray(this.env.group, this.env.address_group_stack); $.each(this.env.address_group_stack, function(i, v) {
if (index < 0) if (ref.env.group == v.id) {
this.env.address_group_stack = []; index = i;
else return false;
this.env.address_group_stack = this.env.address_group_stack.slice(0,index); }
});
this.env.address_group_stack = index < 0 ? [] : this.env.address_group_stack.slice(0, index);
// make sure the current group is on top of the stack // make sure the current group is on top of the stack
if (this.env.group) { if (this.env.group) {
this.env.address_group_stack.push(this.env.group); if (!search) search = {};
search.id = this.env.group;
this.env.address_group_stack.push(search);
// mark the first group on the stack as selected in the directory list // mark the first group on the stack as selected in the directory list
folder = 'G'+src+this.env.address_group_stack[0]; folder = 'G'+src+this.env.address_group_stack[0].id;
} }
else if (this.gui_objects.addresslist_title) { else if (this.gui_objects.addresslist_title) {
$(this.gui_objects.addresslist_title).html(this.get_label('contacts')); $(this.gui_objects.addresslist_title).text(this.get_label('contacts'));
} }
if (!this.env.search_id) if (!this.env.search_id)
@ -4964,7 +4982,9 @@ function rcube_webmail()
var boxtitle = $(this.gui_objects.addresslist_title).html(''); // clear contents var boxtitle = $(this.gui_objects.addresslist_title).html(''); // clear contents
// add link to pop back to parent group // add link to pop back to parent group
if (this.env.address_group_stack.length > 1) { if (this.env.address_group_stack.length > 1
|| (this.env.address_group_stack.length == 1 && this.env.address_group_stack[0].search_request)
) {
$('<a href="#list">...</a>') $('<a href="#list">...</a>')
.attr('title', this.gettext('uponelevel')) .attr('title', this.gettext('uponelevel'))
.addClass('poplink') .addClass('poplink')
@ -4973,10 +4993,11 @@ function rcube_webmail()
boxtitle.append('&nbsp;&raquo;&nbsp;'); boxtitle.append('&nbsp;&raquo;&nbsp;');
} }
boxtitle.append($('<span>').text(prop.name)); boxtitle.append($('<span>').text(prop ? prop.name : this.get_label('contacts')));
} }
this.triggerEvent('groupupdate', prop); if (prop)
this.triggerEvent('groupupdate', prop);
}; };
// load contact record // load contact record

@ -554,30 +554,15 @@ class rcube_ldap extends rcube_addressbook
$this->result = new rcube_result_set($entries['count'], ($this->list_page-1) * $this->page_size); $this->result = new rcube_result_set($entries['count'], ($this->list_page-1) * $this->page_size);
} }
else { else {
$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))
$prop['filter'] = $this->filter;
// exec LDAP search if no result resource is stored // exec LDAP search if no result resource is stored
if ($this->ready && !$this->ldap_result) if ($this->ready && $this->ldap_result === null) {
$this->ldap_result = $this->ldap->search($base_dn, $prop['filter'], $prop['scope'], $this->prop['attributes'], $prop); $this->ldap_result = $this->extended_search();
}
// count contacts for this user // count contacts for this user
$this->result = $this->count(); $this->result = $this->count();
// we have a search result resource $entries = $this->ldap_result;
if ($this->ldap_result && $this->result->count > 0) {
// sorting still on the ldap server
if ($this->sort_col && $prop['scope'] !== 'base' && !$this->ldap->vlv_active)
$this->ldap_result->sort($this->sort_col);
// get all entries from the ldap server
$entries = $this->ldap_result->entries();
}
} // end else } // end else
// start and end of the page // start and end of the page
@ -741,7 +726,7 @@ class rcube_ldap extends rcube_addressbook
* @param boolean $nocount (Not used) * @param boolean $nocount (Not used)
* @param array $required List of fields that cannot be empty * @param array $required List of fields that cannot be empty
* *
* @return array Indexed list of contact records and 'count' value * @return rcube_result_set List of contact records
*/ */
function search($fields, $value, $mode=0, $select=true, $nocount=false, $required=array()) function search($fields, $value, $mode=0, $select=true, $nocount=false, $required=array())
{ {
@ -867,12 +852,10 @@ class rcube_ldap extends rcube_addressbook
// avoid double-wildcard if $value is empty // avoid double-wildcard if $value is empty
$filter = preg_replace('/\*+/', '*', $filter); $filter = preg_replace('/\*+/', '*', $filter);
// add general filter to query
if (!empty($this->prop['filter']))
$filter = '(&(' . preg_replace('/^\(|\)$/', '', $this->prop['filter']) . ')' . $filter . ')';
// set filter string and execute search // set filter string and execute search
$this->set_search_set($filter); // @FIXME: we need a better way to detect/define when groups are allowed in the result
$prefix = empty($required) ? 'e:' : '';
$this->set_search_set($prefix . $filter);
if ($select) if ($select)
$this->list_records(); $this->list_records();
@ -891,24 +874,97 @@ class rcube_ldap extends rcube_addressbook
function count() function count()
{ {
$count = 0; $count = 0;
if ($this->ldap_result) { if (!empty($this->ldap_result)) {
$count = $this->ldap_result->count(); $count = $this->ldap_result['count'];
} }
else if ($this->group_id && $this->group_data['dn']) { else if ($this->group_id && $this->group_data['dn']) {
$count = count($this->list_group_members($this->group_data['dn'], true)); $count = count($this->list_group_members($this->group_data['dn'], true));
} }
// We have a connection but no result set, attempt to get one. // We have a connection but no result set, attempt to get one.
else if ($this->ready) { else if ($this->ready) {
$prop = $this->group_id ? $this->group_data : $this->prop; $count = $this->extended_search(true);
$base_dn = $this->group_id ? $this->group_base_dn : $this->base_dn; }
if (!empty($this->filter)) { // Use global search filter return new rcube_result_set($count, ($this->list_page-1) * $this->page_size);
$prop['filter'] = $this->filter; }
/**
* Wrapper on LDAP searches with group_filters support, which
* allows searching for contacts AND groups.
*
* @param bool $count Return count instead of the records
*
* @return int|array Count of records or the result array (with 'count' item)
*/
protected function extended_search($count = false)
{
$prop = $this->group_id ? $this->group_data : $this->prop;
$base_dn = $this->group_id ? $this->groups_base_dn : $this->base_dn;
$attrs = $count ? array('dn') : $this->prop['attributes'];
$entries = array();
// Use global search filter
if ($filter = $this->filter) {
if ($filter[0] == 'e' && $filter[1] == ':') {
$filter = substr($filter, 2);
$is_extended_search = !$this->group_id;
}
$prop['filter'] = $filter;
// add general filter to query
if (!empty($this->prop['filter'])) {
$prop['filter'] = '(&(' . preg_replace('/^\(|\)$/', '', $this->prop['filter']) . ')' . $prop['filter'] . ')';
} }
$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); $result = $this->ldap->search($base_dn, $prop['filter'], $prop['scope'], $attrs, $prop, $count);
// we have a search result resource, get all entries
if (!$count && $result && $result->count() > 0) {
$result = $result->entries();
unset($result['count']);
}
// search for groups
if ($is_extended_search
&& is_array($this->prop['group_filters'])
&& !empty($this->prop['groups']['filter'])
) {
$filter = '(&(' . preg_replace('/^\(|\)$/', '', $this->prop['groups']['filter']) . ')' . $filter . ')';
// for groups we may use cn instead of displayname...
if ($this->prop['fieldmap']['name'] != $this->prop['groups']['name_attr']) {
$filter = str_replace(strtolower($this->prop['fieldmap']['name']) . '=', $this->prop['groups']['name_attr'] . '=', $filter);
}
$name_attr = $this->prop['groups']['name_attr'];
$email_attr = $this->prop['groups']['email_attr'] ?: 'mail';
$attrs = array_unique(array('dn', 'objectClass', $name_attr, $email_attr));
$res = $this->ldap->search($this->groups_base_dn, $filter, $this->prop['groups']['scope'], $attrs, $prop, $count);
if ($count && $res) {
$result += $res;
}
else if (!$count && $res && $res->count()) {
$res = $res->entries();
unset($res['count']);
$result = array_merge($result, $res);
}
}
if (!$count && $result) {
// sorting
if ($this->sort_col && $prop['scope'] !== 'base' && !$this->ldap->vlv_active) {
usort($result, array($this, '_entry_sort_cmp'));
}
$result['count'] = count($result);
$this->result_entries = $result;
}
return $result;
} }

@ -57,11 +57,13 @@ else {
} }
if ($CONTACTS->group_id) { if ($CONTACTS->group_id) {
$OUTPUT->command('set_group_prop', array('ID' => $CONTACTS->group_id) $group_data = array('ID' => $CONTACTS->group_id)
+ array_intersect_key((array)$CONTACTS->get_group($CONTACTS->group_id), array('name'=>1,'email'=>1))); + array_intersect_key((array)$CONTACTS->get_group($CONTACTS->group_id), array('name'=>1,'email'=>1));
} }
} }
$OUTPUT->command('set_group_prop', $group_data);
// update message count display // update message count display
$OUTPUT->set_env('pagecount', ceil($result->count / $PAGE_SIZE)); $OUTPUT->set_env('pagecount', ceil($result->count / $PAGE_SIZE));
$OUTPUT->command('set_rowcount', rcmail_get_rowcount_text($result)); $OUTPUT->command('set_rowcount', rcmail_get_rowcount_text($result));

@ -229,13 +229,15 @@ function rcmail_contact_search()
} }
// update message count display // update message count display
$OUTPUT->command('set_env', 'search_request', $search_request); $OUTPUT->set_env('search_request', $search_request);
$OUTPUT->command('set_env', 'pagecount', ceil($result->count / $PAGE_SIZE)); $OUTPUT->set_env('pagecount', ceil($result->count / $PAGE_SIZE));
$OUTPUT->command('set_rowcount', rcmail_get_rowcount_text($result)); $OUTPUT->command('set_rowcount', rcmail_get_rowcount_text($result));
// Re-set current source // Re-set current source
$OUTPUT->command('set_env', 'search_id', $sid); $OUTPUT->set_env('search_id', $sid);
$OUTPUT->command('set_env', 'source', ''); $OUTPUT->set_env('source', '');
$OUTPUT->command('set_env', 'group', ''); $OUTPUT->set_env('group', '');
// Re-set list header
$OUTPUT->command('set_group_prop', null);
if (!$sid) { if (!$sid) {
// unselect currently selected directory/group // unselect currently selected directory/group

Loading…
Cancel
Save