Code

76c794911643ede8025ad024d02e2ab0ce648a87
[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();
39   var $pid;
40   var $headpage;
43   function filter($filename)
44   {
45     global $config;
47     // Load eventually passed filename
48     if (!$this->load($filename)) {
49       die("Cannot parse $filename!");
50     }
52     $this->pid= preg_replace("/[^0-9]/", "", microtime(TRUE)); 
53   }
56   function load($filename)
57   {
58     $contents = file_get_contents($filename);
59     $this->xmlData= xml::xml2array($contents, 1);
61     if (!isset($this->xmlData['filterdef'])) {
62       return false;
63     }
65     $this->xmlData= $this->xmlData["filterdef"];
67     // Load filter
68     if (isset($this->xmlData['search'])) {
69       if (!isset($this->xmlData['search']['query'][0])){
70         $this->xmlData['search']['query']= array($this->xmlData['search']['query']);
71       }
73       // Move information
74       $entry= $this->xmlData['search'];
75       $this->scopeMode= $entry['scope'];
76       if ($entry['scope'] == "auto") {
77         $this->scope= "one";
78       } else {
79         $this->scope= $entry['scope'];
80       }
81       $this->query= $entry['query'];
82     } else {
83       return false;
84     }
86     // Transfer initial value
87     if (isset($this->xmlData['definition']['initial']) && $this->xmlData['definition']['initial'] == "true"){
88       $this->initial= true;
89     }
91     // Transfer category
92     if (isset($this->xmlData['definition']['category'])){
93       $this->category= $this->xmlData['definition']['category'];
94     }
96     // Generate formular data
97     if (isset($this->xmlData['element'])) {
98       if (!isset($this->xmlData['element'][0])){
99         $this->xmlData['element']= array($this->xmlData['element']);
100       }
101       foreach ($this->xmlData['element'] as $element) {
103         // Ignore elements without type
104         if (!isset($element['type']) || !isset($element['tag'])) {
105           next;
106         }
108         $tag= $element['tag'];
110         // Fix arrays
111         if (isset($element['value']) && !isset($element['value'][0])) {
112           $element['value']= array($element['value']);
113         }
115         // Store element for quick access
116         $this->elements[$tag] = $element;
118         // Preset elementValues with default values if exist
119         if (isset($element['default']) && !is_array($element['default'])) {
120           $this->elementValues[$tag] = $element['default'];
121         } else {
122           $this->elementValues[$tag] = "";
123         }
125         // Does this element react on alphabet links?
126         if (isset($element['alphabet']) && $element['alphabet'] == "true") {
127           $this->alphabetElements[]= $tag;
128         }
129       }
131       uasort($this->elements, 'strlenSort');
132       $this->elements= array_reverse($this->elements);
134     }
136     return true;  
137   }
140   function getTextfield($element)
141   {
142     $tag= $element['tag'];
143     $size= 30;
144     if (isset($element['size'])){
145       $size= $element['size'];
146     }
147     $maxlength= 30;
148     if (isset($element['maxlength'])){
149       $maxlength= $element['maxlength'];
150     }
151     $result= "<input class='filter_textfield' id='$tag' name='$tag' type='text' size='$size' maxlength='{$maxlength}' value='".$this->elementValues[$tag]."'>";
152     if (isset($element['autocomplete'])) {
153       $frequency= "0.5";
154       $characters= "1";
155       if (isset($element['autocomplete']['frequency'])) {
156         $frequency= $element['autocomplete']['frequency'];
157       }
158       if (isset($element['autocomplete']['characters'])) {
159         $characters= $element['autocomplete']['characters'];
160       }
161       $result.= "<div id='autocomplete$tag' class='autocomplete'></div>".
162                 "<script type='text/javascript'>".
163                 "new Ajax.Autocompleter('$tag', 'autocomplete$tag', 'autocomplete.php', { minChars: $characters, frequency: $frequency });".
164                 "</script>";
166        $this->autocompleters[$tag]= $element['autocomplete'];
167     }
168     return $result;
169   }
172   function getCheckbox($element)
173   {
174     $tag= $element['tag'];
175     $checked= "";
176     if ($this->elementValues[$tag] == "true") {
177       $checked= " checked";
178     }
180     $result= "<input class='filter_checkbox' id='$tag' name='$tag' type='checkbox' onClick='document.mainform.submit();' value='true'$checked>";
181     return $result;
182   }
185   function getCombobox($element)
186   {
187     $result= "<select name='".$element['tag']."' size='1' onChange='document.mainform.submit();'>";
189     // Fill with presets
190     foreach ($element['value'] as $value) {
191       $selected= "";
192       if ($this->elementValues[$element['tag']] == $value['key']) {
193         $selected= " selected";
194       }
196       // Handle translations
197       $result.= "<option value='".$value['key']."'$selected>"._($value['label'])."</option>";
198     }
200     // Use autocompleter for additional data
201     if (isset($element['autocomplete'])) {
202       $list= $this->getCompletitionList($element['autocomplete'], $element['tag']);
203       foreach ($list as $value) {
204         $selected= "";
205         if ($this->elementValues[$element['tag']] == $value) {
206           $selected= " selected";
207         }
208         $result.= "<option value='$value'$selected>$value</option>";
209       }
210     }
212     $result.= "</select>";
214     return $result;
215   }
218   function setComboBoxOptions($tag, $options)
219   {
220     if (isset($this->elements[$tag]) && $this->elements[$tag]['type'] == "combobox") {
221       
222       $this->elements[$tag]['value']= array();
223       foreach ($options as $key => $label) {
224         $this->elements[$tag]['value'][]= array('label' => $label, 'key' => $key);
225       }
226     }
227   }
230   function getCurrentBase()
231   {
232     if (isset($this->search->base) && (string)$this->search->scope != "auto") {
233       return false;
234     }
236     return $this->base;
237   }
240   function getCurrentScope()
241   {
242     if (isset($this->search->scope) && (string)$this->search->scope != "auto") {
243       return (string)$this->search->scope;
244     }
246     return $this->scope;
247   }
250   function setConverter($field, $hook)
251   {
252     $this->converter[$field]= $hook;
253   }
256   function setObjectStorage($storage)
257   {
258     $this->objectStorage= $storage;    
259   }
262   function setBase($base)
263   {
264     $this->base= $base;
265   }
268   function setCurrentScope($scope)
269   {
270     $this->scope= $scope;
271   }
274   function renderAlphabet($columns= 10)
275   {
276     // Return pre-rendered alphabet if available
277     if ($this->alphabet) {
278       return ($this->alphabet);
279     }
281     $characters= _("*ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
282     $alphabet= "";
283     $c= 0;
285     /* Fill cells with charaters */
286     for ($i= 0, $l= mb_strlen($characters, 'UTF8'); $i<$l; $i++){
287       if ($c == 0){
288         $alphabet.= "<tr>";
289       }
291       $ch = mb_substr($characters, $i, 1, "UTF8");
292       $alphabet.= "<td><a class=\"alphaselect\" href=\"main.php?plug=".
293         validate($_GET['plug'])."&amp;filter=".$ch."\">&nbsp;".$ch."&nbsp;</a></td>";
295       if ($c++ == $columns){
296         $alphabet.= "</tr>";
297         $c= 0;
298       }
299     }
301     /* Fill remaining cells */
302     while ($c++ <= $columns){
303       $alphabet.= "<td>&nbsp;</td>";
304     }
306     /* Save alphabet */
307     $this->alphabet= "<table width='100%'>$alphabet</table>";
309     return ($this->alphabet);
310   }
313   function renderApply()
314   {
315     return ("<input type='submit' name='apply' value='"._("Apply filter")."'>");
316   }
319   function renderScope()
320   {
321     $checked= $this->scope == "sub"?" checked":"";
322     return "<input type='checkbox' id='SCOPE' name='SCOPE' value='1' onClick='document.mainform.submit();'$checked>&nbsp;<LABEL for='SCOPE'>"._("Search in subtrees")."</LABEL>";
323   }
326   function render()
327   {
328     $smarty= get_smarty();
329     $smarty->assign("ALPHABET", $this->renderAlphabet());
330     $smarty->assign("APPLY", $this->renderApply());
331     $smarty->assign("SCOPE", $this->renderScope());
333     // Load template and replace elementsHtml[]
334     foreach ($this->elements as $tag => $element) {
335       $htmlCode= "";
336       switch ($element['type']) {
337         case "textfield":
338           $htmlCode = $this->getTextfield($element);
339           break;
341         case "checkbox":
342           $htmlCode = $this->getCheckbox($element);
343           break;
345         case "combobox":
346           $htmlCode = $this->getCombobox($element);
347           break;
349         default:
350           die ("Unknown element type specified!");
351       }
352       $smarty->assign("$tag", $htmlCode);
353     }
355     // Try to load template from plugin the folder first...
356     $file = get_template_path($this->xmlData['definition']['template'], true);
358     // ... if this fails, try to load the file from the theme folder.
359     if(!file_exists($file)){
360       $file = get_template_path($this->xmlData['definition']['template']);
361     }
363     // Load template
364     return ("<input type='hidden' name='FILTER_PID' value='".$this->pid."'>".$smarty->fetch($file));
365   }
368   function query()
369   {
370     global $class_mapping;
371     $result= array();
373     // Return empty list if initial is not set
374     if (!$this->initial) {
375       $this->initial= true;
376       return $result;
377     }
379     // Go thru all queries and merge results
380     foreach ($this->query as $query) {
381       if (!isset($query['backend']) || !isset($query['filter']) || !isset($query['attribute'])) {
382         die("No backend specified in search config.");
383       }
385       // Is backend available?
386       $backend= "filter".$query['backend'];
387       if (!isset($class_mapping["$backend"])) {
388         die("Invalid backend specified in search config.");
389       }
391       // Load filter and attributes
392       $filter= $query['filter'];
393       $attributes= $query['attribute'];
395       // ObjectClass is required to check permissions later.
396       if(!in_array('objectClass', $attributes)) $attributes[] = 'objectClass';
398       // Generate final filter
399       foreach ($this->elements as $tag => $element) {
400         if (!isset($element['set']) || !isset($element['unset'])) {
401           continue;
402         }
404         // Handle converters if present
405         if (isset($this->converter[$tag])) {
406           preg_match('/([^:]+)::(.*)$/', $this->converter[$tag], $m);
407           $e_set= call_user_func(array($m[1], $m[2]), preg_replace('/\$/', $this->elementValues[$tag], is_array($element['set'])?"":$element['set']));
408           $e_unset= call_user_func(array($m[1], $m[2]), preg_replace('/\$/', $this->elementValues[$tag], is_array($element['unset'])?"":$element['unset']));
409         } else {
410           $e_set= is_array($element['set'])?"":$element['set'];
411           $e_unset= is_array($element['unset'])?"":$element['unset'];
412         }
414         // Do not replace escaped \$ - This is required to be able to search for e.g. windows machines.
415         if ($this->elementValues[$tag] == "") {
416           $e_unset= preg_replace('/([^\\\\])\$/', '${1}'.normalizeLdap($this->elementValues[$tag]), $e_unset);
417           $e_unset= preg_replace('/\\\\\$/','$', $e_unset);
418           $filter= preg_replace("/\\$$tag/", $e_unset, $filter);
419         } else {
420           $e_set= preg_replace('/([^\\\\])\$/', '${1}'.normalizeLdap($this->elementValues[$tag]), $e_set);
421           $e_set= preg_replace('/\\\\\$/','$', $e_set);
422           $filter= preg_replace("/\\$$tag/", $e_set, $filter);
423         }
424       }
426       // Now call filter method and merge resulting entries. 
427       $result= array_merge($result, call_user_func(array($backend, 'query'),
428             $this, $this->base, $this->scope, $filter, $attributes, $this->category, $this->objectStorage));
429     }
430     
431     return ($result);
432   }
435   function isValid()
436   {
437     foreach ($this->elements as $tag => $element) {
438       if (isset($element->regex)){
439         if (!preg_match('/'.(string)$element->regex.'/', $this->elementValues[$tag])){
440           return false;
441         }
442       }
443     }
444     return true;
445   }
448   function update()
449   {
450     /* React on alphabet links if needed */
451     if (isset($_GET['filter'])){
452       $s= mb_substr(validate($_GET['filter']), 0, 1, "UTF8");
453       foreach ($this->alphabetElements as $tag) {
454         $this->elementValues[$tag]= $s;
455       }
456     }
458     if (isset($_POST['FILTER_PID']) && $_POST['FILTER_PID'] == $this->pid) {
459       // Load post values and adapt filter, base and scope accordingly - but
460       // only if we didn't get a _GET
461       foreach ($this->elements as $tag => $element) {
462         if (isset($_POST[$tag])){
463           $this->elementValues[$tag]= validate($_POST[$tag]);
464         } else {
465           $this->elementValues[$tag]= "";
466         }
467       }
469       // Save scope if needed
470       if ($this->scopeMode == "auto") {
471         $this->scope= isset($_POST['SCOPE'])?"sub":"one";
472       }
473     }
475   }
478   function getCompletitionList($config, $tag, $value="*")
479   {
480     global $class_mapping;
481     $res= array();
483     // Is backend available?
484     $backend= "filter".$config['backend'];
485     if (!isset($class_mapping["$backend"])) {
486       die("Invalid backend specified in search config.");
487     }
489     // Load filter and attributes
490     $filter= $config['filter'];
491     $attributes= $config['attribute'];
492     if (!is_array($attributes)) {
493       $attributes= array($attributes);
494     }
496     // ObjectClass is required to check permissions later.
497     if(!in_array('objectClass', $attributes)) $attributes[] = 'objectClass';
499     // Make filter
500     $filter= preg_replace("/\\$$tag/", normalizeLdap($value), $filter);
501     if (isset($config['base']) && isset($config['scope']) && isset($config['category'])) {
502       $result= call_user_func(array($backend, 'query'), $this,$config['base'], $config['scope'], $filter, $attributes,
503                            $config["category"], $config["objectStorage"]);
504     } else {
505       $result= call_user_func(array($backend, 'query'), $this,$this->base, $this->scope, $filter, $attributes,
506                            $this->category, $this->objectStorage);
507     }
509     foreach ($result as $entry) {
510       foreach ($attributes as $attribute) {
511         if (is_array($entry[$attribute])) {
512           for ($i= 0; $i<$entry[$attribute]['count']; $i++) {
513             if (mb_stristr($entry[$attribute][$i], $value)) {
514               $res[]= $entry[$attribute][$i];
515             }
516           }
517         } else {
518           $res[]= $entry[$attribute];
519         }
520       }
521     }
523     return $res;
524   }
527   function processAutocomplete()
528   {
529     global $class_mapping;
530     $result= array();
532     // Introduce maximum number of entries
533     $max= 25;
535     foreach ($this->autocompleters as $tag => $config) {
536       if(isset($_POST[$tag])){
537         $result= $this->getCompletitionList($config, $tag, $_POST[$tag]);
538         $result= array_unique($result);
539         asort($result);
541         echo '<ul>';
542         foreach ($result as $entry) {
543           echo '<li>'.mark($_POST[$tag], $entry).'</li>';
544           if ($max-- == 0) {
545             break;
546           }
547         }
549         echo '</ul>';
550       }
551     }
552   }
555   function getObjectBase($dn)
556   {
557     global $config;
558     $base= "";
560     // Try every object storage
561     $storage= $this->objectStorage;
562     if (!is_array($storage)){
563       $storage= array($storage);
564     }
565     foreach ($storage as $location) {
566       $pattern= "/^[^,]+,".preg_quote($location, '/')."/i";
567       $base= preg_replace($pattern, '', $dn);
568     }
570     /* Set to base, if we're not on a correct subtree */
571     if (!isset($config->idepartments[$base])){
572       $base= $config->current['BASE'];
573     }
575     return $base;
576   }
581 // Sort elements for element length to allow proper replacing later on
582 function strlenSort($a, $b) {
583   if (strlen($a['tag']) == strlen($b['tag'])) {
584     return 0;
585   }
586   return (strlen($a['tag']) < strlen($b['tag']) ? -1 : 1);
587
589 ?>