]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/LdapCommon/extlib/Net/LDAP2/Filter.php
Updated LDAP2 extlib to latest version.
[quix0rs-gnu-social.git] / plugins / LdapCommon / extlib / Net / LDAP2 / Filter.php
1 <?php
2 /* vim: set expandtab tabstop=4 shiftwidth=4: */
3 /**
4 * File containing the Net_LDAP2_Filter interface class.
5 *
6 * PHP version 5
7 *
8 * @category  Net
9 * @package   Net_LDAP2
10 * @author    Benedikt Hallinger <beni@php.net>
11 * @copyright 2009 Benedikt Hallinger
12 * @license   http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
13 * @version   SVN: $Id: Filter.php 332305 2013-12-09 08:51:41Z beni $
14 * @link      http://pear.php.net/package/Net_LDAP2/
15 */
16
17 /**
18 * Includes
19 */
20 require_once 'PEAR.php';
21 require_once 'Net/LDAP2/Util.php';
22 require_once 'Net/LDAP2/Entry.php';
23
24 /**
25 * Object representation of a part of a LDAP filter.
26 *
27 * This Class is not completely compatible to the PERL interface!
28 *
29 * The purpose of this class is, that users can easily build LDAP filters
30 * without having to worry about right escaping etc.
31 * A Filter is built using several independent filter objects
32 * which are combined afterwards. This object works in two
33 * modes, depending how the object is created.
34 * If the object is created using the {@link create()} method, then this is a leaf-object.
35 * If the object is created using the {@link combine()} method, then this is a container object.
36 *
37 * LDAP filters are defined in RFC-2254 and can be found under
38 * {@link http://www.ietf.org/rfc/rfc2254.txt}
39 *
40 * Here a quick copy&paste example:
41 * <code>
42 * $filter0 = Net_LDAP2_Filter::create('stars', 'equals', '***');
43 * $filter_not0 = Net_LDAP2_Filter::combine('not', $filter0);
44 *
45 * $filter1 = Net_LDAP2_Filter::create('gn', 'begins', 'bar');
46 * $filter2 = Net_LDAP2_Filter::create('gn', 'ends', 'baz');
47 * $filter_comp = Net_LDAP2_Filter::combine('or',array($filter_not0, $filter1, $filter2));
48 *
49 * echo $filter_comp->asString();
50 * // This will output: (|(!(stars=\0x5c0x2a\0x5c0x2a\0x5c0x2a))(gn=bar*)(gn=*baz))
51 * // The stars in $filter0 are treaten as real stars unless you disable escaping.
52 * </code>
53 *
54 * @category Net
55 * @package  Net_LDAP2
56 * @author   Benedikt Hallinger <beni@php.net>
57 * @license  http://www.gnu.org/copyleft/lesser.html LGPL
58 * @link     http://pear.php.net/package/Net_LDAP2/
59 */
60 class Net_LDAP2_Filter extends PEAR
61 {
62     /**
63     * Storage for combination of filters
64     *
65     * This variable holds a array of filter objects
66     * that should be combined by this filter object.
67     *
68     * @access protected
69     * @var array
70     */
71     protected $_subfilters = array();
72
73     /**
74     * Match of this filter
75     *
76     * If this is a leaf filter, then a matching rule is stored,
77     * if it is a container, then it is a logical operator
78     *
79     * @access protected
80     * @var string
81     */
82     protected $_match;
83
84     /**
85     * Single filter
86     *
87     * If we operate in leaf filter mode,
88     * then the constructing method stores
89     * the filter representation here
90     *
91     * @acces private
92     * @var string
93     */
94     protected $_filter;
95
96     /**
97     * Create a new Net_LDAP2_Filter object and parse $filter.
98     *
99     * This is for PERL Net::LDAP interface.
100     * Construction of Net_LDAP2_Filter objects should happen through either
101     * {@link create()} or {@link combine()} which give you more control.
102     * However, you may use the perl iterface if you already have generated filters.
103     *
104     * @param string $filter LDAP filter string
105     *
106     * @see parse()
107     */
108     public function __construct($filter = false)
109     {
110         // The optional parameter must remain here, because otherwise create() crashes
111         if (false !== $filter) {
112             $filter_o = self::parse($filter);
113             if (PEAR::isError($filter_o)) {
114                 $this->_filter = $filter_o; // assign error, so asString() can report it
115             } else {
116                 $this->_filter = $filter_o->asString();
117             }
118         }
119     }
120
121     /**
122     * Constructor of a new part of a LDAP filter.
123     *
124     * The following matching rules exists:
125     *    - equals:         One of the attributes values is exactly $value
126     *                      Please note that case sensitiviness is depends on the
127     *                      attributes syntax configured in the server.
128     *    - begins:         One of the attributes values must begin with $value
129     *    - ends:           One of the attributes values must end with $value
130     *    - contains:       One of the attributes values must contain $value
131     *    - present | any:  The attribute can contain any value but must be existent
132     *    - greater:        The attributes value is greater than $value
133     *    - less:           The attributes value is less than $value
134     *    - greaterOrEqual: The attributes value is greater or equal than $value
135     *    - lessOrEqual:    The attributes value is less or equal than $value
136     *    - approx:         One of the attributes values is similar to $value
137     *
138     * Negation ("not") can be done by prepending the above operators with the
139     * "not" or "!" keyword, see example below. 
140     *
141     * If $escape is set to true (default) then $value will be escaped
142     * properly. If it is set to false then $value will be treaten as raw filter value string.
143     * You should escape yourself using {@link Net_LDAP2_Util::escape_filter_value()}!
144     *
145     * Examples:
146     * <code>
147     *   // This will find entries that contain an attribute "sn" that ends with "foobar":
148     *   $filter = Net_LDAP2_Filter::create('sn', 'ends', 'foobar');
149     *
150     *   // This will find entries that contain an attribute "sn" that has any value set:
151     *   $filter = Net_LDAP2_Filter::create('sn', 'any');
152     *
153     *   // This will build a negated equals filter:
154     *   $filter = Net_LDAP2_Filter::create('sn', 'not equals', 'foobar');
155     * </code>
156     *
157     * @param string  $attr_name Name of the attribute the filter should apply to
158     * @param string  $match     Matching rule (equals, begins, ends, contains, greater, less, greaterOrEqual, lessOrEqual, approx, any)
159     * @param string  $value     (optional) if given, then this is used as a filter
160     * @param boolean $escape    Should $value be escaped? (default: yes, see {@link Net_LDAP2_Util::escape_filter_value()} for detailed information)
161     *
162     * @return Net_LDAP2_Filter|Net_LDAP2_Error
163     */
164     public static function &create($attr_name, $match, $value = '', $escape = true)
165     {
166         $leaf_filter = new Net_LDAP2_Filter();
167         if ($escape) {
168             $array = Net_LDAP2_Util::escape_filter_value(array($value));
169             $value = $array[0];
170         }
171
172         $match = strtolower($match);
173
174         // detect negation
175         $neg_matches   = array();
176         $negate_filter = false;
177         if (preg_match('/^(?:not|!)[\s_-](.+)/', $match, $neg_matches)) {
178             $negate_filter = true;
179             $match         = $neg_matches[1];
180         }
181
182         // build basic filter
183         switch ($match) {
184         case 'equals':
185         case '=':
186         case '==':
187             $leaf_filter->_filter = '(' . $attr_name . '=' . $value . ')';
188             break;
189         case 'begins':
190             $leaf_filter->_filter = '(' . $attr_name . '=' . $value . '*)';
191             break;
192         case 'ends':
193             $leaf_filter->_filter = '(' . $attr_name . '=*' . $value . ')';
194             break;
195         case 'contains':
196             $leaf_filter->_filter = '(' . $attr_name . '=*' . $value . '*)';
197             break;
198         case 'greater':
199         case '>':
200             $leaf_filter->_filter = '(' . $attr_name . '>' . $value . ')';
201             break;
202         case 'less':
203         case '<':
204             $leaf_filter->_filter = '(' . $attr_name . '<' . $value . ')';
205             break;
206         case 'greaterorequal':
207         case '>=':
208             $leaf_filter->_filter = '(' . $attr_name . '>=' . $value . ')';
209             break;
210         case 'lessorequal':
211         case '<=':
212             $leaf_filter->_filter = '(' . $attr_name . '<=' . $value . ')';
213             break;
214         case 'approx':
215         case '~=':
216             $leaf_filter->_filter = '(' . $attr_name . '~=' . $value . ')';
217             break;
218         case 'any':
219         case 'present': // alias that may improve user code readability
220             $leaf_filter->_filter = '(' . $attr_name . '=*)';
221             break;
222         default:
223             return PEAR::raiseError('Net_LDAP2_Filter create error: matching rule "' . $match . '" not known!');
224         }
225         
226         // negate if requested
227         if ($negate_filter) {
228            $leaf_filter = Net_LDAP2_Filter::combine('!', $leaf_filter);
229         }
230
231         return $leaf_filter;
232     }
233
234     /**
235     * Combine two or more filter objects using a logical operator
236     *
237     * This static method combines two or more filter objects and returns one single
238     * filter object that contains all the others.
239     * Call this method statically: $filter = Net_LDAP2_Filter::combine('or', array($filter1, $filter2))
240     * If the array contains filter strings instead of filter objects, we will try to parse them.
241     *
242     * @param string                 $log_op  The locical operator. May be "and", "or", "not" or the subsequent logical equivalents "&", "|", "!"
243     * @param array|Net_LDAP2_Filter $filters array with Net_LDAP2_Filter objects
244     *
245     * @return Net_LDAP2_Filter|Net_LDAP2_Error
246     * @static
247     */
248     public static function &combine($log_op, $filters)
249     {
250         if (PEAR::isError($filters)) {
251             return $filters;
252         }
253
254         // substitude named operators to logical operators
255         if ($log_op == 'and') $log_op = '&';
256         if ($log_op == 'or')  $log_op = '|';
257         if ($log_op == 'not') $log_op = '!';
258
259         // tests for sane operation
260         if ($log_op == '!') {
261             // Not-combination, here we only accept one filter object or filter string
262             if ($filters instanceof Net_LDAP2_Filter) {
263                 $filters = array($filters); // force array
264             } elseif (is_string($filters)) {
265                 $filter_o = self::parse($filters);
266                 if (PEAR::isError($filter_o)) {
267                     $err = PEAR::raiseError('Net_LDAP2_Filter combine error: '.$filter_o->getMessage());
268                     return $err;
269                 } else {
270                     $filters = array($filter_o);
271                 }
272             } elseif (is_array($filters)) {
273                 if (count($filters) != 1) {
274                     $err = PEAR::raiseError('Net_LDAP2_Filter combine error: operator is "not" but $filter is an array!');
275                     return $err;
276                 } elseif (!($filters[0] instanceof Net_LDAP2_Filter)) {
277                      $err = PEAR::raiseError('Net_LDAP2_Filter combine error: operator is "not" but $filter is not a valid Net_LDAP2_Filter nor a filter string!');
278                      return $err;
279                 }
280             } else {
281                 $err = PEAR::raiseError('Net_LDAP2_Filter combine error: operator is "not" but $filter is not a valid Net_LDAP2_Filter nor a filter string!');
282                 return $err;
283             }
284         } elseif ($log_op == '&' || $log_op == '|') {
285             if (!is_array($filters) || count($filters) < 2) {
286                 $err = PEAR::raiseError('Net_LDAP2_Filter combine error: parameter $filters is not an array or contains less than two Net_LDAP2_Filter objects!');
287                 return $err;
288             }
289         } else {
290             $err = PEAR::raiseError('Net_LDAP2_Filter combine error: logical operator is not known!');
291             return $err;
292         }
293
294         $combined_filter = new Net_LDAP2_Filter();
295         foreach ($filters as $key => $testfilter) {     // check for errors
296             if (PEAR::isError($testfilter)) {
297                 return $testfilter;
298             } elseif (is_string($testfilter)) {
299                 // string found, try to parse into an filter object
300                 $filter_o = self::parse($testfilter);
301                 if (PEAR::isError($filter_o)) {
302                     return $filter_o;
303                 } else {
304                     $filters[$key] = $filter_o;
305                 }
306             } elseif (!$testfilter instanceof Net_LDAP2_Filter) {
307                 $err = PEAR::raiseError('Net_LDAP2_Filter combine error: invalid object passed in array $filters!');
308                 return $err;
309             }
310         }
311
312         $combined_filter->_subfilters = $filters;
313         $combined_filter->_match      = $log_op;
314         return $combined_filter;
315     }
316
317     /**
318     * Parse FILTER into a Net_LDAP2_Filter object
319     *
320     * This parses an filter string into Net_LDAP2_Filter objects.
321     *
322     * @param string $FILTER The filter string
323     *
324     * @access static
325     * @return Net_LDAP2_Filter|Net_LDAP2_Error
326     * @todo Leaf-mode: Do we need to escape at all? what about *-chars?check for the need of encoding values, tackle problems (see code comments)
327     */
328     public static function parse($FILTER)
329     {
330         if (preg_match('/^\((.+?)\)$/', $FILTER, $matches)) {
331             // Check for right bracket syntax: count of unescaped opening
332             // brackets must match count of unescaped closing brackets.
333             // At this stage we may have:
334             //   1. one filter component with already removed outer brackets
335             //   2. one or more subfilter components
336             $c_openbracks  = preg_match_all('/(?<!\\\\)\(/' , $matches[1], $notrelevant);
337             $c_closebracks = preg_match_all('/(?<!\\\\)\)/' , $matches[1], $notrelevant);
338             if ($c_openbracks != $c_closebracks) {
339                 return PEAR::raiseError("Filter parsing error: invalid filter syntax - opening brackets do not match close brackets!");
340             }
341
342             if (in_array(substr($matches[1], 0, 1), array('!', '|', '&'))) {
343                 // Subfilter processing: pass subfilters to parse() and combine
344                 // the objects using the logical operator detected
345                 // we have now something like "&(...)(...)(...)" but at least one part ("!(...)").
346                 // Each subfilter could be an arbitary complex subfilter.
347
348                 // extract logical operator and filter arguments
349                 $log_op              = substr($matches[1], 0, 1);
350                 $remaining_component = substr($matches[1], 1);
351
352                 // split $remaining_component into individual subfilters
353                 // we cannot use split() for this, because we do not know the
354                 // complexiness of the subfilter. Thus, we look trough the filter
355                 // string and just recognize ending filters at the first level.
356                 // We record the index number of the char and use that information
357                 // later to split the string.
358                 $sub_index_pos = array();
359                 $prev_char     = ''; // previous character looked at
360                 $level         = 0;  // denotes the current bracket level we are,
361                                      //   >1 is too deep, 1 is ok, 0 is outside any
362                                      //   subcomponent
363                 for ($curpos = 0; $curpos < strlen($remaining_component); $curpos++) {
364                     $cur_char = substr($remaining_component, $curpos, 1);
365
366                     // rise/lower bracket level
367                     if ($cur_char == '(' && $prev_char != '\\') {
368                         $level++;
369                     } elseif  ($cur_char == ')' && $prev_char != '\\') {
370                         $level--;
371                     }
372
373                     if ($cur_char == '(' && $prev_char == ')' && $level == 1) {
374                         array_push($sub_index_pos, $curpos); // mark the position for splitting
375                     }
376                     $prev_char = $cur_char;
377                 }
378
379                 // now perform the splits. To get also the last part, we
380                 // need to add the "END" index to the split array
381                 array_push($sub_index_pos, strlen($remaining_component));
382                 $subfilters = array();
383                 $oldpos = 0;
384                 foreach ($sub_index_pos as $s_pos) {
385                     $str_part = substr($remaining_component, $oldpos, $s_pos - $oldpos);
386                     array_push($subfilters, $str_part);
387                     $oldpos = $s_pos;
388                 }
389
390                 // some error checking...
391                 if (count($subfilters) == 1) {
392                     // only one subfilter found
393                 } elseif (count($subfilters) > 1) {
394                     // several subfilters found
395                     if ($log_op == "!") {
396                         return PEAR::raiseError("Filter parsing error: invalid filter syntax - NOT operator detected but several arguments given!");
397                     }
398                 } else {
399                     // this should not happen unless the user specified a wrong filter
400                     return PEAR::raiseError("Filter parsing error: invalid filter syntax - got operator '$log_op' but no argument!");
401                 }
402
403                 // Now parse the subfilters into objects and combine them using the operator
404                 $subfilters_o = array();
405                 foreach ($subfilters as $s_s) {
406                     $o = self::parse($s_s);
407                     if (PEAR::isError($o)) {
408                         return $o;
409                     } else {
410                         array_push($subfilters_o, self::parse($s_s));
411                     }
412                 }
413
414                 $filter_o = self::combine($log_op, $subfilters_o);
415                 return $filter_o;
416
417             } else {
418                 // This is one leaf filter component, do some syntax checks, then escape and build filter_o
419                 // $matches[1] should be now something like "foo=bar"
420
421                 // detect multiple leaf components
422                 // [TODO] Maybe this will make problems with filters containing brackets inside the value
423                 if (stristr($matches[1], ')(')) {
424                     return PEAR::raiseError("Filter parsing error: invalid filter syntax - multiple leaf components detected!");
425                 } else {
426                     $filter_parts = Net_LDAP2_Util::split_attribute_string($matches[1], true, true);
427                     if (count($filter_parts) != 3) {
428                         return PEAR::raiseError("Filter parsing error: invalid filter syntax - unknown matching rule used");
429                     } else {
430                         $filter_o          = new Net_LDAP2_Filter();
431                         // [TODO]: Do we need to escape at all? what about *-chars user provide and that should remain special?
432                         //         I think, those prevent escaping! We need to check against PERL Net::LDAP!
433                         // $value_arr         = Net_LDAP2_Util::escape_filter_value(array($filter_parts[2]));
434                         // $value             = $value_arr[0];
435                         $value             = $filter_parts[2];
436                         $filter_o->_filter = '('.$filter_parts[0].$filter_parts[1].$value.')';
437                         return $filter_o;
438                     }
439                 }
440             }
441         } else {
442                // ERROR: Filter components must be enclosed in round brackets
443                return PEAR::raiseError("Filter parsing error: invalid filter syntax - filter components must be enclosed in round brackets");
444         }
445     }
446
447     /**
448     * Get the string representation of this filter
449     *
450     * This method runs through all filter objects and creates
451     * the string representation of the filter. If this
452     * filter object is a leaf filter, then it will return
453     * the string representation of this filter.
454     *
455     * @return string|Net_LDAP2_Error
456     */
457     public function asString()
458     {
459         if ($this->isLeaf()) {
460             $return = $this->_filter;
461         } else {
462             $return = '';
463             foreach ($this->_subfilters as $filter) {
464                 $return = $return.$filter->asString();
465             }
466             $return = '(' . $this->_match . $return . ')';
467         }
468         return $return;
469     }
470
471     /**
472     * Alias for perl interface as_string()
473     *
474     * @see asString()
475     * @return string|Net_LDAP2_Error
476     */
477     public function as_string()
478     {
479         return $this->asString();
480     }
481
482     /**
483     * Print the text representation of the filter to FH, or the currently selected output handle if FH is not given
484     *
485     * This method is only for compatibility to the perl interface.
486     * However, the original method was called "print" but due to PHP language restrictions,
487     * we can't have a print() method.
488     *
489     * @param resource $FH (optional) A filehandle resource
490     *
491     * @return true|Net_LDAP2_Error
492     */
493     public function printMe($FH = false)
494     {
495         if (!is_resource($FH)) {
496             if (PEAR::isError($FH)) {
497                 return $FH;
498             }
499             $filter_str = $this->asString();
500             if (PEAR::isError($filter_str)) {
501                 return $filter_str;
502             } else {
503                 print($filter_str);
504             }
505         } else {
506             $filter_str = $this->asString();
507             if (PEAR::isError($filter_str)) {
508                 return $filter_str;
509             } else {
510                 $res = @fwrite($FH, $this->asString());
511                 if ($res == false) {
512                     return PEAR::raiseError("Unable to write filter string to filehandle \$FH!");
513                 }
514             }
515         }
516         return true;
517     }
518
519     /**
520     * This can be used to escape a string to provide a valid LDAP-Filter.
521     *
522     * LDAP will only recognise certain characters as the
523     * character istself if they are properly escaped. This is
524     * what this method does.
525     * The method can be called statically, so you can use it outside
526     * for your own purposes (eg for escaping only parts of strings)
527     *
528     * In fact, this is just a shorthand to {@link Net_LDAP2_Util::escape_filter_value()}.
529     * For upward compatibiliy reasons you are strongly encouraged to use the escape
530     * methods provided by the Net_LDAP2_Util class.
531     *
532     * @param string $value Any string who should be escaped
533     *
534     * @static
535     * @return string         The string $string, but escaped
536     * @deprecated  Do not use this method anymore, instead use Net_LDAP2_Util::escape_filter_value() directly
537     */
538     public static function escape($value)
539     {
540         $return = Net_LDAP2_Util::escape_filter_value(array($value));
541         return $return[0];
542     }
543
544     /**
545     * Is this a container or a leaf filter object?
546     *
547     * @access protected
548     * @return boolean
549     */
550     protected function isLeaf()
551     {
552         if (count($this->_subfilters) > 0) {
553             return false; // Container!
554         } else {
555             return true; // Leaf!
556         }
557     }
558
559     /**
560     * Filter entries using this filter or see if a filter matches
561     *
562     * @todo Currently slow and naive implementation with preg_match, could be optimized (esp. begins, ends filters etc)
563     * @todo Currently only "="-based matches (equals, begins, ends, contains, any) implemented; Implement all the stuff!
564     * @todo Implement expert code with schema checks in case $entry is connected to a directory
565     * @param array|Net_LDAP2_Entry The entry (or array with entries) to check
566     * @param array                 If given, the array will be appended with entries who matched the filter. Return value is true if any entry matched.
567     * @return int|Net_LDAP2_Error Returns the number of matched entries or error
568     */
569     function matches(&$entries, &$results=array()) {
570         $numOfMatches = 0;
571
572         if (!is_array($entries)) {
573             $all_entries = array(&$entries);
574         } else {
575             $all_entries = &$entries;
576         }
577
578         foreach ($all_entries as $entry) {
579             // look at the current entry and see if filter matches
580
581             $entry_matched = false;
582             // if this is not a single component, do calculate all subfilters,
583             // then assert the partial results with the given combination modifier
584             if (!$this->isLeaf()) {
585         
586                 // get partial results from subfilters
587                 $partial_results = array();
588                 foreach ($this->_subfilters as $filter) {
589                     $partial_results[] = $filter->matches($entry);
590                 }
591             
592                 // evaluate partial results using this filters combination rule
593                 switch ($this->_match) {
594                     case '!':
595                         // result is the neagtive result of the assertion
596                         $entry_matched = !$partial_results[0];
597                     break;
598
599                     case '&':
600                         // all partial results have to be boolean-true
601                         $entry_matched = !in_array(false, $partial_results);
602                     break;
603                 
604                     case '|':
605                         // at least one partial result has to be true
606                         $entry_matched = in_array(true, $partial_results);
607                     break;
608                 }
609             
610             } else {
611                 // Leaf filter: assert given entry
612                 // [TODO]: Could be optimized to avoid preg_match especially with "ends", "begins" etc
613             
614                 // Translate the LDAP-match to some preg_match expression and evaluate it
615                 list($attribute, $match, $assertValue) = $this->getComponents();
616                 switch ($match) {
617                     case '=':
618                         $regexp = '/^'.str_replace('*', '.*', $assertValue).'$/i'; // not case sensitive unless specified by schema
619                         $entry_matched = $entry->pregMatch($regexp, $attribute);
620                     break;
621                 
622                     // -------------------------------------
623                     // [TODO]: implement <, >, <=, >= and =~
624                     // -------------------------------------
625                 
626                     default:
627                         $err = PEAR::raiseError("Net_LDAP2_Filter match error: unsupported match rule '$match'!");
628                         return $err;
629                 }
630             
631             }
632
633             // process filter matching result
634             if ($entry_matched) {
635                 $numOfMatches++;
636                 $results[] = $entry;
637             }
638
639         }
640
641         return $numOfMatches;
642     }
643
644
645     /**
646     * Retrieve this leaf-filters attribute, match and value component.
647     *
648     * For leaf filters, this returns array(attr, match, value).
649     * Match is be the logical operator, not the text representation,
650     * eg "=" instead of "equals". Note that some operators are really
651     * a combination of operator+value with wildcard, like
652     * "begins": That will return "=" with the value "value*"!
653     *
654     * For non-leaf filters this will drop an error.
655     *
656     * @todo $this->_match is not always available and thus not usable here; it would be great if it would set in the factory methods and constructor.
657     * @return array|Net_LDAP2_Error
658     */
659     function getComponents() {
660         if ($this->isLeaf()) {
661             $raw_filter = preg_replace('/^\(|\)$/', '', $this->_filter);
662             $parts = Net_LDAP2_Util::split_attribute_string($raw_filter, true, true);
663             if (count($parts) != 3) {
664                 return PEAR::raiseError("Net_LDAP2_Filter getComponents() error: invalid filter syntax - unknown matching rule used");
665             } else {
666                 return $parts;
667             }
668         } else {
669             return PEAR::raiseError('Net_LDAP2_Filter getComponents() call is invalid for non-leaf filters!');
670         }
671     }
672
673
674 }
675 ?>