Implement LDAPv3 Virtual List View (VLV) for paged results listing

release-0.6
thomascube 13 years ago
parent 9f38ad95de
commit 69ea3a3429

@ -1,6 +1,7 @@
CHANGELOG Roundcube Webmail
===========================
- Implement LDAPv3 Virtual List View (VLV) for paged results listing
- Use 'address_template' config option when adding a new address block (#1487944)
- Added addressbook advanced search
- Add popup with basic fields selection for addressbook search

@ -534,6 +534,7 @@ $rcmail_config['ldap_public']['Verisign'] = array(
'scope' => 'sub', // search mode: sub|base|list
'filter' => '(objectClass=inetOrgPerson)', // used for basic listing (if not empty) and will be &'d with search queries. example: status=act
'fuzzy_search' => true, // server allows wildcard search
'vlv' => false, // Enable Virtual List View to mor efficiently fetch paginated data (if server supports it)
'sizelimit' => '0', // Enables you to limit the count of entries fetched. Setting this to 0 means no limit.
'timelimit' => '0', // Sets the number of seconds how long is spend on the search. Setting this to 0 means no limit.

@ -54,6 +54,9 @@ class rcube_ldap extends rcube_addressbook
private $group_cache = array();
private $group_members = array();
private $vlv_active = false;
private $vlv_count = 0;
/**
* Object constructor
@ -417,10 +420,11 @@ class rcube_ldap extends rcube_addressbook
// we have a search result resource
if ($this->ldap_result && $this->result->count > 0)
{
if ($this->sort_col && $this->prop['scope'] !== 'base')
if ($this->sort_col && $this->prop['scope'] !== 'base' && !$this->vlv_active)
ldap_sort($this->conn, $this->ldap_result, $this->sort_col);
$start_row = $subset < 0 ? $this->result->first + $this->page_size + $subset : $this->result->first;
$start_row = $this->vlv_active ? 0 : $this->result->first;
$start_row = $subset < 0 ? $start_row + $this->page_size + $subset : $start_row;
$last_row = $this->result->first + $this->page_size;
$last_row = $subset != 0 ? $start_row + abs($subset) : $last_row;
@ -547,7 +551,7 @@ class rcube_ldap extends rcube_addressbook
{
$count = 0;
if ($this->conn && $this->ldap_result) {
$count = ldap_count_entries($this->conn, $this->ldap_result);
$count = $this->vlv_active ? $this->vlv_count : ldap_count_entries($this->conn, $this->ldap_result);
} // end if
elseif ($this->conn) {
// We have a connection but no result set, attempt to get one.
@ -555,7 +559,7 @@ class rcube_ldap extends rcube_addressbook
// The filter is not set, set it.
$this->filter = $this->prop['filter'];
} // end if
$this->_exec_search();
$this->_exec_search(true);
if ($this->ldap_result) {
$count = ldap_count_entries($this->conn, $this->ldap_result);
} // end if
@ -856,7 +860,7 @@ class rcube_ldap extends rcube_addressbook
*
* @access private
*/
private function _exec_search()
private function _exec_search($all = false)
{
if ($this->ready)
{
@ -865,6 +869,13 @@ class rcube_ldap extends rcube_addressbook
$this->_debug("C: Search [".$filter."]");
// when using VLV, we need to issue listing command first in order to get the full count
if (!$all && $function != 'ldap_read' && $this->prop['vlv']) {
$this->_exec_search(true);
$this->vlv_count = ldap_count_entries($this->conn, $this->ldap_result);
$this->vlv_active = $this->_vlv_set_controls();
}
if ($this->ldap_result = @$function($this->conn, $this->base_dn, $filter,
array_values($this->fieldmap), 0, (int) $this->prop['sizelimit'], (int) $this->prop['timelimit']))
{
@ -879,6 +890,26 @@ class rcube_ldap extends rcube_addressbook
return false;
}
/**
* Set server controls for Virtual List View (paginated listing)
*/
private function _vlv_set_controls()
{
$sort_ctrl = array('oid' => "1.2.840.113556.1.4.473", 'value' => $this->_sort_ber_encode(array($this->sort_col)));
$vlv_ctrl = array('oid' => "2.16.840.1.113730.3.4.9", 'value' => $this->_vlv_ber_encode(($offset = ($this->list_page-1) * $this->page_size + 1), $this->page_size), 'iscritical' => true);
//$this->_debug("C: set controls sort=" . join(' ', unpack('H'.(strlen($sort_ctrl['value'])*2), $sort_ctrl['value'])) . " ({$this->sort_col});"
// . " vlv=" . join(' ', (unpack('H'.(strlen($vlv_ctrl['value'])*2), $vlv_ctrl['value']))) . " ($offset)");
if (!ldap_set_option($this->conn, LDAP_OPT_SERVER_CONTROLS, array($sort_ctrl, $vlv_ctrl))) {
$this->_debug("S: ".ldap_error($this->conn));
$this->set_error(self::ERROR_SEARCH, 'vlvnotsupported');
return false;
}
return true;
}
/**
@ -1228,4 +1259,119 @@ class rcube_ldap extends rcube_addressbook
}
return $groups;
}
/**
* Generate BER encoded string for Virtual List View option
*
* @param integer List offset (first record)
* @param integer Records per page
* @return string BER encoded option value
*/
private function _vlv_ber_encode($offset, $rpp)
{
# this string is ber-encoded, php will prefix this value with:
# 04 (octet string) and 10 (length of 16 bytes)
# the code behind this string is broken down as follows:
# 30 = ber sequence with a length of 0e (14) bytes following
# 20 = type integer (in two's complement form) with 2 bytes following (beforeCount): 01 00 (ie 0)
# 20 = type integer (in two's complement form) with 2 bytes following (afterCount): 01 18 (ie 25-1=24)
# a0 = type context-specific/constructed with a length of 06 (6) bytes following
# 20 = type integer with 2 bytes following (offset): 01 01 (ie 1)
# 20 = type integer with 2 bytes following (contentCount): 01 00
# the following info was taken from the ISO/IEC 8825-1:2003 x.690 standard re: the
# encoding of integer values (note: these values are in
# two-complement form so since offset will never be negative bit 8 of the
# leftmost octet should never by set to 1):
# 8.3.2: If the contents octets of an integer value encoding consist
# of more than one octet, then the bits of the first octet (rightmost) and bit 8
# of the second (to the left of first octet) octet:
# a) shall not all be ones; and
# b) shall not all be zero
# construct the string from right to left
$str = "020100"; # contentCount
$ber_val = self::_ber_encode_int($offset); // returns encoded integer value in hex format
// calculate octet length of $ber_val
$str = self::_ber_addseq($ber_val, '02') . $str;
// now compute length over $str
$str = self::_ber_addseq($str, 'a0');
// now tack on records per page
$str = sprintf("0201000201%02x", min(255, $rpp)-1) . $str;
// now tack on sequence identifier and length
$str = self::_ber_addseq($str, '30');
return pack('H'.strlen($str), $str);
}
/**
* create ber encoding for sort control
*
* @pararm array List of cols to sort by
* @return string BER encoded option value
*/
private function _sort_ber_encode($sortcols)
{
$str = '';
foreach (array_reverse((array)$sortcols) as $col) {
$ber_val = self::_string2hex($col);
# 30 = ber sequence with a length of octet value
# 04 = octet string with a length of the ascii value
$oct = self::_ber_addseq($ber_val, '04');
$str = self::_ber_addseq($oct, '30') . $str;
}
// now tack on sequence identifier and length
$str = self::_ber_addseq($str, '30');
return pack('H'.strlen($str), $str);
}
/**
* Add BER sequence with correct length and the given identifier
*/
private static function _ber_addseq($str, $identifier)
{
$len = sprintf("%x", strlen($str)/2);
if (strlen($len) % 2 != 0)
$len = '0'.$len;
return $identifier . $len . $str;
}
/**
* Returns BER encoded integer value in hex format
*/
private static function _ber_encode_int($offset)
{
$val = sprintf("%x", $offset);
$prefix = '';
// check if bit 8 of high byte is 1
if (preg_match('/^[89abcdef]/', $val))
$prefix = '00';
if (strlen($val)%2 != 0)
$prefix .= '0';
return $prefix . $val;
}
/**
* Returns ascii string encoded in hex
*/
private static function _string2hex($str) {
$hex = '';
for ($i=0; $i < strlen($str); $i++)
$hex .= dechex(ord($str[$i]));
return $hex;
}
}

Loading…
Cancel
Save