diff --git a/lib/Net/LDAP3.php b/lib/Net/LDAP3.php
index badedb5d2ac88d215a9a4593483c54ab6e5ee6d1..2662e915f3643db7aa224c1bf8587b619537c2d1 100644
--- a/lib/Net/LDAP3.php
+++ b/lib/Net/LDAP3.php
@@ -44,7 +44,7 @@ class Net_LDAP3
     const CONTROL_EFFECTIVE_RIGHTS = '1.3.6.1.4.1.42.2.27.9.5.2';
     const CONTROL_SORT_REQUEST     = '1.2.840.113556.1.4.473';
     const CONTROL_VLV_REQUEST      = '2.16.840.1.113730.3.4.9';
-
+    const CONTROL_VLV_RESPONSE     = '2.16.840.1.113730.3.4.10';
 
     public $conn;
     public $vlv_active = false;
@@ -1625,14 +1625,17 @@ class Net_LDAP3
                 }
                 $this->_debug("D: total numsubordinates = " . $vlv_count);
             }
-            // ...or by fetching all records dn and count them
-            else if (!function_exists('ldap_parse_virtuallist_control')) {
+            // ...or by parsing the controls in the response, and if that's not supported
+            // by fetching all records dn and counting them
+            else if (PHP_VERSION_ID < 70305 && !function_exists('ldap_parse_virtuallist_control')) {
                 // @FIXME: this search will ignore $props['search']
                 $vlv_count = $this->search($base_dn, $filter, $scope, array('dn'), $props, true);
             }
 
-            $this->vlv_active = $this->_vlv_set_controls($sort, $this->list_page, $this->page_size,
+            $controls = $this->_vlv_set_controls($sort, $this->list_page, $this->page_size,
                 $this->_vlv_search($sort, $props['search']));
+
+            $this->vlv_active = (bool) $controls;
         }
         else {
             $this->vlv_active = false;
@@ -1668,18 +1671,32 @@ class Net_LDAP3
 
         $this->_debug("Executing search with return attributes: " . var_export($attrs, true));
 
-        $ldap_result = @$function($this->conn, $base_dn, $filter, $attrs, 0, $sizelimit, $timelimit);
+        if (is_array($controls)) {
+            $ldap_result = $function($this->conn, $base_dn, $filter, $attrs, 0, $sizelimit, $timelimit, LDAP_DEREF_NEVER, $controls);
+        }
+        else {
+            $ldap_result = @$function($this->conn, $base_dn, $filter, $attrs, 0, $sizelimit, $timelimit);
+        }
 
         if (!$ldap_result) {
             $this->_warning("LDAP: $function failed for dn=$base_dn. " . ldap_error($this->conn));
             return false;
         }
 
-        // when running on a patched PHP we can use the extended functions
-        // to retrieve the total count from the LDAP search result
-        if ($this->vlv_active && function_exists('ldap_parse_virtuallist_control')) {
+        // when running on a PHP with server controls support we can
+        // retrieve the total count from the LDAP search result
+        if ($this->vlv_active && (is_array($controls) || function_exists('ldap_parse_virtuallist_control'))) {
             if (ldap_parse_result($this->conn, $ldap_result, $errcode, $matcheddn, $errmsg, $referrals, $serverctrls)) {
-                ldap_parse_virtuallist_control($this->conn, $serverctrls, $last_offset, $vlv_count, $vresult);
+                if (PHP_VERSION_ID >= 70300) {
+                    $vlv_count   = (int) $serverctrls[self::CONTROL_VLV_RESPONSE]['value']['count'];
+                    // FIXME: I don't know this is the same offset value as in ldap_parse_virtuallist_control() below
+                    //        but anyway it looks like we do not use that value for anything
+                    $last_offset = (int) $serverctrls[self::CONTROL_VLV_RESPONSE]['value']['target'];
+                }
+                else {
+                    ldap_parse_virtuallist_control($this->conn, $serverctrls, $last_offset, $vlv_count, $vresult);
+                }
+
                 $this->_debug("S: VLV result: last_offset=$last_offset; content_count=$vlv_count");
             }
             else {
@@ -2598,39 +2615,6 @@ class Net_LDAP3
         }
     }
 
-    /**
-     * Add BER sequence with correct length and the given identifier
-     */
-    private static function _ber_addseq($str, $identifier)
-    {
-        $len = dechex(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    = dechex($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;
-    }
-
     /**
      * Quotes attribute value string
      *
@@ -2705,6 +2689,107 @@ class Net_LDAP3
         return implode(',', $result);
     }
 
+    private function _fuzzy_search_prefix()
+    {
+        switch ($this->config_get("fuzzy_search", 2)) {
+            case 2:
+                return "*";
+                break;
+            case 1:
+            case 0:
+            default:
+                return "";
+                break;
+        }
+    }
+
+    private function _fuzzy_search_suffix()
+    {
+        switch ($this->config_get("fuzzy_search", 2)) {
+            case 2:
+                return "*";
+                break;
+            case 1:
+                return "*";
+            case 0:
+            default:
+                return "";
+                break;
+        }
+    }
+
+    /**
+     * Return the search string value to be used in VLV controls
+     *
+     * @param array        $sort   List of attributes in vlv index
+     * @param array|string $search Search string or attribute => value hash
+     *
+     * @return string Search string
+     */
+    private function _vlv_search($sort, $search)
+    {
+        if (!empty($this->additional_filter)) {
+            $this->_debug("Not setting a VLV search filter because we already have a filter");
+            return;
+        }
+
+        if (empty($search)) {
+            return;
+        }
+
+        foreach ((array) $search as $attr => $value) {
+            if ($attr && !in_array(strtolower($attr), $sort)) {
+                $this->_debug("Cannot use VLV search using attribute not indexed: $attr (not in " . var_export($sort, true) . ")");
+                return;
+            }
+            else {
+                return $value . $this->_fuzzy_search_suffix();
+            }
+        }
+    }
+
+    /**
+     * Set server controls for Virtual List View (paginated listing)
+     */
+    private function _vlv_set_controls($sort, $list_page, $page_size, $search = null)
+    {
+        $sort_ctrl = array(
+            'oid'   => self::CONTROL_SORT_REQUEST,
+            'value' => self::_sort_ber_encode($sort)
+        );
+
+        if (!empty($search)) {
+            $this->_debug("_vlv_set_controls to include search: " . var_export($search, true));
+        }
+
+        $vlv_ctrl  = array(
+            'oid' => self::CONTROL_VLV_REQUEST,
+            'value' => self::_vlv_ber_encode(
+                    $offset = ($list_page-1) * $page_size + 1,
+                    $page_size,
+                    $search
+            ),
+            'iscritical' => true
+        );
+
+        $this->_debug("C: set controls sort=" . join(' ', unpack('H'.(strlen($sort_ctrl['value'])*2), $sort_ctrl['value']))
+            . " (" . implode(',', (array) $sort) . ");"
+            . " vlv=" . join(' ', (unpack('H'.(strlen($vlv_ctrl['value'])*2), $vlv_ctrl['value']))) . " ($offset/$page_size)");
+
+        $controls = array($sort_ctrl, $vlv_ctrl);
+
+        if (PHP_VERSION_ID >= 70305) {
+            return $controls;
+        }
+
+        if (!ldap_set_option($this->conn, LDAP_OPT_SERVER_CONTROLS, $controls)) {
+            $this->_debug("S: ".ldap_error($this->conn));
+            return false;
+        }
+
+        return true;
+    }
+
     /**
      * create ber encoding for sort control
      *
@@ -2729,14 +2814,6 @@ class Net_LDAP3
         return pack('H'.strlen($str), $str);
     }
 
-    /**
-     * Returns ascii string encoded in hex
-     */
-    private static function _string2hex($str)
-    {
-        return implode(unpack("H*", $str));
-    }
-
     /**
      * Generate BER encoded string for Virtual List View option
      *
@@ -2799,101 +2876,45 @@ class Net_LDAP3
         return pack('H'.strlen($str), $str);
     }
 
-    private function _fuzzy_search_prefix()
+    /**
+     * Add BER sequence with correct length and the given identifier
+     */
+    private static function _ber_addseq($str, $identifier)
     {
-        switch ($this->config_get("fuzzy_search", 2)) {
-            case 2:
-                return "*";
-                break;
-            case 1:
-            case 0:
-            default:
-                return "";
-                break;
+        $len = dechex(strlen($str)/2);
+        if (strlen($len) % 2 != 0) {
+            $len = '0'.$len;
         }
-    }
 
-    private function _fuzzy_search_suffix()
-    {
-        switch ($this->config_get("fuzzy_search", 2)) {
-            case 2:
-                return "*";
-                break;
-            case 1:
-                return "*";
-            case 0:
-            default:
-                return "";
-                break;
-        }
+        return $identifier . $len . $str;
     }
 
     /**
-     * Return the search string value to be used in VLV controls
-     *
-     * @param array        $sort   List of attributes in vlv index
-     * @param array|string $search Search string or attribute => value hash
-     *
-     * @return string Search string
+     * Returns BER encoded integer value in hex format
      */
-    private function _vlv_search($sort, $search)
+    private static function _ber_encode_int($offset)
     {
-        if (!empty($this->additional_filter)) {
-            $this->_debug("Not setting a VLV search filter because we already have a filter");
-            return;
-        }
+        $val    = dechex($offset);
+        $prefix = '';
 
-        if (empty($search)) {
-            return;
+        // check if bit 8 of high byte is 1
+        if (preg_match('/^[89abcdef]/', $val)) {
+            $prefix = '00';
         }
 
-        foreach ((array) $search as $attr => $value) {
-            if ($attr && !in_array(strtolower($attr), $sort)) {
-                $this->_debug("Cannot use VLV search using attribute not indexed: $attr (not in " . var_export($sort, true) . ")");
-                return;
-            }
-            else {
-                return $value . $this->_fuzzy_search_suffix();
-            }
+        if (strlen($val)%2 != 0) {
+            $prefix .= '0';
         }
+
+        return $prefix . $val;
     }
 
     /**
-     * Set server controls for Virtual List View (paginated listing)
+     * Returns ascii string encoded in hex
      */
-    private function _vlv_set_controls($sort, $list_page, $page_size, $search = null)
+    private static function _string2hex($str)
     {
-        $sort_ctrl = array(
-            'oid'   => self::CONTROL_SORT_REQUEST,
-            'value' => self::_sort_ber_encode($sort)
-        );
-
-        if (!empty($search)) {
-            $this->_debug("_vlv_set_controls to include search: " . var_export($search, true));
-        }
-
-        $vlv_ctrl  = array(
-            'oid' => self::CONTROL_VLV_REQUEST,
-            'value' => self::_vlv_ber_encode(
-                    $offset = ($list_page-1) * $page_size + 1,
-                    $page_size,
-                    $search
-            ),
-            'iscritical' => true
-        );
-
-        $this->_debug("C: set controls sort=" . join(' ', unpack('H'.(strlen($sort_ctrl['value'])*2), $sort_ctrl['value']))
-            . " (" . implode(',', (array) $sort) . ");"
-            . " vlv=" . join(' ', (unpack('H'.(strlen($vlv_ctrl['value'])*2), $vlv_ctrl['value']))) . " ($offset/$page_size)");
-
-        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;
+        return implode(unpack("H*", $str));
     }
 
     /**
diff --git a/lib/Net/LDAP3/Result.php b/lib/Net/LDAP3/Result.php
index 9b428cfc3605a78787c1923da3a9c9c11dd94ef0..ef57cc8f189bcaa46a6da6b9fa0c78fae2434440 100644
--- a/lib/Net/LDAP3/Result.php
+++ b/lib/Net/LDAP3/Result.php
@@ -42,6 +42,7 @@ class Net_LDAP3_Result implements Iterator
     protected $base_dn;
     protected $filter;
     protected $scope;
+    protected $result;
 
     private $count;
     private $current;