Code

Updated filter definitions
[gosa.git] / gosa-core / include / class_filter.inc
1 <?php
2 /*
3  * This code is part of GOsa (http://www.gosa-project.org)
4  * Copyright (C) 2003-2008 GONICUS GmbH
5  *
6  * ID: $$Id$$
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  */
23 class filter {
25   var $xmlData;
26   var $elements= array();
27   var $elementValues= array();
28   var $alphabetElements= array();
29   var $autocompleter= array();
30   var $category= "";
31   var $objectStorage= array();
32   var $base= "";
33   var $scope= "";
34   var $query;
35   var $initial= false;
36   var $scopeMode= "auto";
37   var $alphabet= null;
38   var $converter= array();
41   function filter($filename)
42   {
43     global $config;
45     // Load eventually passed filename
46     if (!$this->load($filename)) {
47       die("Cannot parse $filename!");
48     }
49   }
52   function load($filename)
53   {
54     $contents = file_get_contents($filename);
55     $this->xmlData= xml::xml2array($contents, 1);
57     if (!isset($this->xmlData['filterdef'])) {
58       return false;
59     }
61     $this->xmlData= $this->xmlData["filterdef"];
63     // Load filter
64     if (isset($this->xmlData['search'])) {
65       if (!isset($this->xmlData['search']['query'][0])){
66         $this->xmlData['search']['query']= array($this->xmlData['search']['query']);
67       }
69       // Move information
70       $entry= $this->xmlData['search'];
71       $this->scopeMode= $entry['scope'];
72       if ($entry['scope'] == "auto") {
73         $this->scope= "one";
74       } else {
75         $this->scope= $entry['scope'];
76       }
77       $this->query= $entry['query'];
78     } else {
79       return false;
80     }
82     // Transfer initial value
83     if (isset($this->xmlData['definition']['initial']) && $this->xmlData['definition']['initial'] == "true"){
84       $this->initial= true;
85     }
87     // Transfer category
88     if (isset($this->xmlData['definition']['category'])){
89       $this->category= $this->xmlData['definition']['category'];
90     }
92     // Generate formular data
93     if (isset($this->xmlData['element'])) {
94       if (!isset($this->xmlData['element'][0])){
95         $this->xmlData['element']= array($this->xmlData['element']);
96       }
97       foreach ($this->xmlData['element'] as $element) {
99         // Ignore elements without type
100         if (!isset($element['type']) || !isset($element['tag'])) {
101           next;
102         }
104         $tag= $element['tag'];
106         // Fix arrays
107         if (isset($element['value']) && !isset($element['value'][0])) {
108           $element['value']= array($element['value']);
109         }
111         // Store element for quick access
112         $this->elements[$tag] = $element;
114         // Preset elementValues with default values if exist
115         if (isset($element['default']) && !is_array($element['default'])) {
116           $this->elementValues[$tag] = $element['default'];
117         } else {
118           $this->elementValues[$tag] = "";
119         }
121         // Does this element react on alphabet links?
122         if (isset($element['alphabet']) && $element['alphabet'] == "true") {
123           $this->alphabetElements[]= $tag;
124         }
125       }
127       // Sort elements for element length to allow proper replacing later on
128       function strlenSort($a, $b) {
129         if (strlen($a['tag']) == strlen($b['tag'])) {
130           return 0;
131         }
132        return (strlen($a['tag']) < strlen($b['tag']) ? -1 : 1);
133       } 
134       uasort($this->elements, 'strlenSort');
135       $this->elements= array_reverse($this->elements);
137     }
139     return true;  
140   }
143   function getTextfield($element)
144   {
145     $tag= $element['tag'];
146     $size= 30;
147     if (isset($element['size'])){
148       $size= $element['size'];
149     }
150     $maxlength= 30;
151     if (isset($element['maxlength'])){
152       $maxlength= $element['maxlength'];
153     }
154     $result= "<input class='filter_textfield' id='$tag' name='$tag' type='text' size='$size' maxlength='maxlength' value='".$this->elementValues[$tag]."'>";
155     if (isset($element['autocomplete'])) {
156       $frequency= "0.5";
157       $characters= "1";
158       if (isset($element['autocomplete']['frequency'])) {
159         $frequency= $element['autocomplete']['frequency'];
160       }
161       if (isset($element['autocomplete']['characters'])) {
162         $characters= $element['autocomplete']['characters'];
163       }
164       $result.= "<div id='autocomplete$tag' class='autocomplete'></div>".
165                 "<script type='text/javascript'>".
166                 "new Ajax.Autocompleter('$tag', 'autocomplete$tag', 'autocomplete.php', { minChars: $characters, frequency: $frequency });".
167                 "</script>";
169        $this->autocompleters[$tag]= $element['autocomplete'];
170     }
171     return $result;
172   }
175   function getCheckbox($element)
176   {
177     $tag= $element['tag'];
178     $checked= "";
179     if ($this->elementValues[$tag] == "true") {
180       $checked= " checked";
181     }
183     $result= "<input class='filter_checkbox' name='$tag' type='checkbox' onClick='document.mainform.submit();' value='true'$checked>";
184     return $result;
185   }
188   function getCombobox($element)
189   {
190     $result= "<select name='".$element['tag']."' size='1' onClick='document.mainform.submit();'>";
192     // Fill with presets
193     foreach ($element['value'] as $value) {
194       $selected= "";
195       if ($this->elementValues[$element['tag']] == $value['key']) {
196         $selected= " selected";
197       }
199       // Handle translations
200       $result.= "<option value='".$value['key']."'$selected>"._($value['label'])."</option>";
201     }
203     // Use autocompleter for additional data
204     if (isset($element['autocomplete'])) {
205       $list= $this->getCompletitionList($element['autocomplete'], $element['tag']);
206       foreach ($list as $value) {
207         $selected= "";
208         if ($this->elementValues[$element['tag']] == $value) {
209           $selected= " selected";
210         }
211         $result.= "<option value='$value'$selected>$value</option>";
212       }
213     }
215     $result.= "</select>";
217     return $result;
218   }
221   function getCurrentBase()
222   {
223     if (isset($this->search->base) && (string)$this->search->scope != "auto") {
224       return false;
225     }
227     return $this->base;
228   }
231   function getCurrentScope()
232   {
233     if (isset($this->search->scope) && (string)$this->search->scope != "auto") {
234       return (string)$this->search->scope;
235     }
237     return $this->scope;
238   }
241   function setConverter($field, $hook)
242   {
243     $this->converter[$field]= $hook;
244   }
247   function setObjectStorage($storage)
248   {
249     $this->objectStorage= $storage;    
250   }
253   function setBase($base)
254   {
255     $this->base= $base;
256   }
259   function setCurrentScope($scope)
260   {
261     $this->scope= $scope;
262   }
265   function renderAlphabet($columns= 10)
266   {
267     // Return pre-rendered alphabet if available
268     if ($this->alphabet) {
269       return ($this->alphabet);
270     }
272     $characters= _("*ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
273     $alphabet= "";
274     $c= 0;
276     /* Fill cells with charaters */
277     for ($i= 0, $l= mb_strlen($characters, 'UTF8'); $i<$l; $i++){
278       if ($c == 0){
279         $alphabet.= "<tr>";
280       }
282       $ch = mb_substr($characters, $i, 1, "UTF8");
283       $alphabet.= "<td><a class=\"alphaselect\" href=\"main.php?plug=".
284         validate($_GET['plug'])."&amp;filter=".$ch."\">&nbsp;".$ch."&nbsp;</a></td>";
286       if ($c++ == $columns){
287         $alphabet.= "</tr>";
288         $c= 0;
289       }
290     }
292     /* Fill remaining cells */
293     while ($c++ <= $columns){
294       $alphabet.= "<td>&nbsp;</td>";
295     }
297     /* Save alphabet */
298     $this->alphabet= "<table width='100%'>$alphabet</table>";
300     return ($this->alphabet);
301   }
304   function renderApply()
305   {
306     return ("<input type='submit' name='apply' value='"._("Apply filter")."'>");
307   }
310   function renderScope()
311   {
312     $checked= $this->scope == "sub"?" checked":"";
313     return "<input type='checkbox' name='SCOPE' value='1' onClick='document.mainform.submit();'$checked>&nbsp;"._("Search in subtrees");
314   }
317   function render()
318   {
319     $smarty= get_smarty();
320     $smarty->assign("ALPHABET", $this->renderAlphabet());
321     $smarty->assign("APPLY", $this->renderApply());
322     $smarty->assign("SCOPE", $this->renderScope());
324     // Load template and replace elementsHtml[]
325     foreach ($this->elements as $tag => $element) {
326       $htmlCode= "";
327       switch ($element['type']) {
328         case "textfield":
329           $htmlCode = $this->getTextfield($element);
330           break;
332         case "checkbox":
333           $htmlCode = $this->getCheckbox($element);
334           break;
336         case "combobox":
337           $htmlCode = $this->getCombobox($element);
338           break;
340         default:
341           die ("Unknown element type specified!");
342       }
343       $smarty->assign("$tag", $htmlCode);
344     }
346     // Load template
347     return ("<input type='hidden' name='FILTER_LOADED' value='1'>".$smarty->fetch(get_template_path($this->xmlData['definition']['template'], true)));
348   }
351   function query()
352   {
353     global $class_mapping;
354     $result= array();
356     // Return empty list if initial is not set
357     if (!$this->initial) {
358       $this->initial= true;
359       return $result;
360     }
362     // Go thru all queries and merge results
363     foreach ($this->query as $query) {
364       if (!isset($query['backend']) || !isset($query['filter']) || !isset($query['attribute'])) {
365         die("No backend specified in search config.");
366       }
368       // Is backend available?
369       $backend= "filter".$query['backend'];
370       if (!isset($class_mapping["$backend"])) {
371         die("Invalid backend specified in search config.");
372       }
374       // Load filter and attributes
375       $filter= $query['filter'];
376       $attributes= $query['attribute'];
378       // Generate final filter
379       foreach ($this->elements as $tag => $element) {
380         if (!isset($element['set']) || !isset($element['unset'])) {
381           continue;
382         }
384         // Handle converters if present
385         if (isset($this->converter[$tag])) {
386           preg_match('/([^:]+)::(.*)$/', $this->converter[$tag], $m);
387           $e_set= call_user_func(array($m[1], $m[2]), preg_replace('/\$/', $this->elementValues[$tag], is_array($element['set'])?"":$element['set']));
388           $e_unset= call_user_func(array($m[1], $m[2]), preg_replace('/\$/', $this->elementValues[$tag], is_array($element['unset'])?"":$element['unset']));
389         } else {
390           $e_set= is_array($element['set'])?"":$element['set'];
391           $e_unset= is_array($element['unset'])?"":$element['unset'];
392         }
394         if ($this->elementValues[$tag] == "") {
395           $e_unset= preg_replace('/\$/', normalizeLdap($this->elementValues[$tag]), $e_unset);
396           $filter= preg_replace("/\\$$tag/", $e_unset, $filter);
397         } else {
398           $e_set= preg_replace('/\$/', normalizeLdap($this->elementValues[$tag]), $e_set);
399           $filter= preg_replace("/\\$$tag/", $e_set, $filter);
400         }
401       }
403       $result= array_merge($result, call_user_func(array($backend, 'query'), $this->base, $this->scope, $filter, $attributes, $this->category, $this->objectStorage));
404     }
405     
406     return ($result);
407   }
410   function isValid()
411   {
412     foreach ($this->elements as $tag => $element) {
413       if (isset($element->regex)){
414         if (!preg_match('/'.(string)$element->regex.'/', $this->elementValues[$tag])){
415           return false;
416         }
417       }
418     }
419     return true;
420   }
423   function update()
424   {
426     /* React on alphabet links if needed */
427     if (isset($_GET['filter'])){
428       $s= mb_substr(validate($_GET['filter']), 0, 1, "UTF8")."*";
429       if ($s == "**"){
430         $s= "*";
431       }
432       foreach ($this->alphabetElements as $tag) {
433         $this->elementValues[$tag]= $s;
434       }
435     }
437     if (isset($_POST['FILTER_LOADED'])) {
438       // Load post values and adapt filter, base and scope accordingly - but
439       // only if we didn't get a _GET
440       foreach ($this->elements as $tag => $element) {
441         if (isset($_POST[$tag])){
442           $this->elementValues[$tag]= validate($_POST[$tag]);
443         } else {
444           $this->elementValues[$tag]= "";
445         }
446       }
448       // Save scope if needed
449       if ($this->scopeMode == "auto") {
450         $this->scope= isset($_POST['SCOPE'])?"sub":"one";
451       }
452     }
454   }
457   function getCompletitionList($config, $tag, $value="*")
458   {
459     global $class_mapping;
460     $res= array();
462     // Is backend available?
463     $backend= "filter".$config['backend'];
464     if (!isset($class_mapping["$backend"])) {
465       die("Invalid backend specified in search config.");
466     }
468     // Load filter and attributes
469     $filter= $config['filter'];
470     $attributes= $config['attribute'];
471     if (!is_array($attributes)) {
472       $attributes= array($attributes);
473     }
475     // Make filter
476     $filter= preg_replace("/\\$$tag/", normalizeLDAP($value), $filter);
477     if (isset($config['base']) && isset($config['scope']) && isset($config['category'])) {
478       $result= call_user_func(array($backend, 'query'), $config['base'], $config['scope'], $filter, $attributes,
479                            $config["category"], $config["objectStorage"]);
480     } else {
481       $result= call_user_func(array($backend, 'query'), $this->base, $this->scope, $filter, $attributes,
482                            $this->category, $this->objectStorage);
483     }
485     foreach ($result as $entry) {
486       foreach ($attributes as $attribute) {
487         if (is_array($entry[$attribute])) {
488           for ($i= 0; $i<$entry[$attribute]['count']; $i++) {
489             $res[]= $entry[$attribute][$i];
490           }
491         } else {
492           $res[]= $entry[$attribute];
493         }
494       }
495     }
497     return $res;
498   }
501   function processAutocomplete()
502   {
503     global $class_mapping;
504     $result= array();
506     // Introduce maximum number of entries
507     $max= 25;
509     foreach ($this->autocompleters as $tag => $config) {
510       if(isset($_POST[$tag])){
511         $result= $this->getCompletitionList($config, $tag, $_POST[$tag]);
512         $result= array_unique($result);
513         asort($result);
515         echo '<ul>';
516         foreach ($result as $entry) {
517           echo '<li>'.$entry.'</li>';
518           if ($max-- == 0) {
519             break;
520           }
521         }
523         echo '</ul>';
524       }
525     }
526   }
531 ?>