Skip to content

Commit

Permalink
Searching in both contacts and groups when LDAP addressbook with grou…
Browse files Browse the repository at this point in the history
…p_filters option is used
  • Loading branch information
alecpl committed Jun 20, 2016
1 parent de7b0b5 commit bbbe607
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 57 deletions.
1 change: 1 addition & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
CHANGELOG Roundcube Webmail
===========================

- Searching in both contacts and groups when LDAP addressbook with group_filters option is used
- Update TinyMCE to version 4.3.13 (#5309)
- Enigma: Delete user keys when using deluser.sh script
- Enigma: Fix redundant list-secret-keys/list-public-keys calls on signing/encryption
Expand Down
65 changes: 43 additions & 22 deletions program/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -1278,21 +1278,37 @@ function rcube_webmail()
break;

case 'pushgroup':
// add group ID to stack
this.env.address_group_stack.push(props.id);
// add group ID and current search to stack
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)
rcube_event.cancel(event);

case 'listgroup':
this.reset_qsearch();
this.list_contacts(props.source, props.id);
this.list_contacts(props.source, props.id, 1, group);
break;

case 'popgroup':
if (this.env.address_group_stack.length > 1) {
this.env.address_group_stack.pop();
if (this.env.address_group_stack.length) {
var old = this.env.address_group_stack.pop();
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;

Expand Down Expand Up @@ -5501,9 +5517,9 @@ function rcube_webmail()
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,
target = window;

Expand All @@ -5513,9 +5529,6 @@ function rcube_webmail()
if (refresh)
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) {
page = this.env.current_page = 1;
this.reset_qsearch();
Expand All @@ -5532,21 +5545,26 @@ function rcube_webmail()
this.env.group = group;

// truncate groups listing stack
var index = $.inArray(this.env.group, this.env.address_group_stack);
if (index < 0)
this.env.address_group_stack = [];
else
this.env.address_group_stack = this.env.address_group_stack.slice(0,index);
$.each(this.env.address_group_stack, function(i, v) {
if (ref.env.group == v.id) {
index = i;
return false;
}
});

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
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
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) {
$(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)
Expand Down Expand Up @@ -5619,7 +5637,9 @@ function rcube_webmail()
var boxtitle = $(this.gui_objects.addresslist_title).html(''); // clear contents

// 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>')
.attr('title', this.get_label('uponelevel'))
.addClass('poplink')
Expand All @@ -5628,10 +5648,11 @@ function rcube_webmail()
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
Expand Down
123 changes: 90 additions & 33 deletions program/lib/Roundcube/rcube_ldap.php
Original file line number Diff line number Diff line change
Expand Up @@ -567,30 +567,15 @@ function list_records($cols=null, $subset=0)
$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;
$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
if ($this->ready && !$this->ldap_result)
$this->ldap_result = $this->ldap->search($base_dn, $prop['filter'], $prop['scope'], $this->prop['attributes'], $prop);
if ($this->ready && $this->ldap_result === null) {
$this->ldap_result = $this->extended_search();
}

// count contacts for this user
$this->result = $this->count();

// we have a search result resource
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();
}

$entries = $this->ldap_result;
} // end else

// start and end of the page
Expand Down Expand Up @@ -753,7 +738,7 @@ function _entry_sort_cmp($a, $b)
* @param boolean $nocount (Not used)
* @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())
{
Expand Down Expand Up @@ -879,12 +864,10 @@ function search($fields, $value, $mode=0, $select=true, $nocount=false, $require
// avoid double-wildcard if $value is empty
$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
$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)
$this->list_records();
Expand All @@ -902,24 +885,98 @@ function search($fields, $value, $mode=0, $select=true, $nocount=false, $require
function count()
{
$count = 0;
if ($this->ldap_result) {
$count = $this->ldap_result->count();
if (!empty($this->ldap_result)) {
$count = $this->ldap_result['count'];
}
else if ($this->group_id && $this->group_data['dn']) {
$count = count($this->list_group_members($this->group_data['dn'], true));
}
// 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;
$base_dn = $this->group_id ? $this->group_base_dn : $this->base_dn;
$count = $this->extended_search(true);
}

if (!empty($this->filter)) { // Use global search filter
$prop['filter'] = $this->filter;
return new rcube_result_set($count, ($this->list_page-1) * $this->page_size);
}

/**
* 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'])
&& $this->groups_base_dn != $base_dn
) {
$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;
}

/**
Expand Down
6 changes: 4 additions & 2 deletions program/steps/addressbook/list.inc
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,13 @@ else {
}

if ($CONTACTS->group_id) {
$OUTPUT->command('set_group_prop', array('ID' => $CONTACTS->group_id)
+ array_intersect_key((array)$CONTACTS->get_group($CONTACTS->group_id), array('name'=>1,'email'=>1)));
$group_data = array('ID' => $CONTACTS->group_id)
+ 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
$OUTPUT->set_env('pagecount', ceil($result->count / $PAGE_SIZE));
$OUTPUT->command('set_rowcount', rcmail_get_rowcount_text($result));
Expand Down
2 changes: 2 additions & 0 deletions program/steps/addressbook/search.inc
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,8 @@ function rcmail_contact_search()
$OUTPUT->set_env('search_id', $sid);
$OUTPUT->set_env('source', '');
$OUTPUT->set_env('group', '');
// Re-set list header
$OUTPUT->command('set_group_prop', null);

if (!$sid) {
// unselect currently selected directory/group
Expand Down

0 comments on commit bbbe607

Please sign in to comment.