Code

Repair wrong line in schema...
[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;
42   function filter($filename)
43   {
44     global $config;
46     // Load eventually passed filename
47     if (!$this->load($filename)) {
48       die("Cannot parse $filename!");
49     }
51     $this->pid= preg_replace("/[^0-9]/", "", microtime(TRUE)); 
52   }
55   function load($filename)
56   {
57     $contents = file_get_contents($filename);
58     $this->xmlData= xml::xml2array($contents, 1);
60     if (!isset($this->xmlData['filterdef'])) {
61       return false;
62     }
64     $this->xmlData= $this->xmlData["filterdef"];
66     // Load filter
67     if (isset($this->xmlData['search'])) {
68       if (!isset($this->xmlData['search']['query'][0])){
69         $this->xmlData['search']['query']= array($this->xmlData['search']['query']);
70       }
72       // Move information
73       $entry= $this->xmlData['search'];
74       $this->scopeMode= $entry['scope'];
75       if ($entry['scope'] == "auto") {
76         $this->scope= "one";
77       } else {
78         $this->scope= $entry['scope'];
79       }
80       $this->query= $entry['query'];
81     } else {
82       return false;
83     }
85     // Transfer initial value
86     if (isset($this->xmlData['definition']['initial']) && $this->xmlData['definition']['initial'] == "true"){
87       $this->initial= true;
88     }
90     // Transfer category
91     if (isset($this->xmlData['definition']['category'])){
92       $this->category= $this->xmlData['definition']['category'];
93     }
95     // Generate formular data
96     if (isset($this->xmlData['element'])) {
97       if (!isset($this->xmlData['element'][0])){
98         $this->xmlData['element']= array($this->xmlData['element']);
99       }
100       foreach ($this->xmlData['element'] as $element) {
102         // Ignore elements without type
103         if (!isset($element['type']) || !isset($element['tag'])) {
104           next;
105         }
107         $tag= $element['tag'];
109         // Fix arrays
110         if (isset($element['value']) && !isset($element['value'][0])) {
111           $element['value']= array($element['value']);
112         }
114         // Store element for quick access
115         $this->elements[$tag] = $element;
117         // Preset elementValues with default values if exist
118         if (isset($element['default']) && !is_array($element['default'])) {
119           $this->elementValues[$tag] = $element['default'];
120         } else {
121           $this->elementValues[$tag] = "";
122         }
124         // Does this element react on alphabet links?
125         if (isset($element['alphabet']) && $element['alphabet'] == "true") {
126           $this->alphabetElements[]= $tag;
127         }
128       }
130       // Sort elements for element length to allow proper replacing later on
131       function strlenSort($a, $b) {
132         if (strlen($a['tag']) == strlen($b['tag'])) {
133           return 0;
134         }
135        return (strlen($a['tag']) < strlen($b['tag']) ? -1 : 1);
136       } 
137       uasort($this->elements, 'strlenSort');
138       $this->elements= array_reverse($this->elements);
140     }
142     return true;  
143   }
146   function getTextfield($element)
147   {
148     $tag= $element['tag'];
149     $size= 30;
150     if (isset($element['size'])){
151       $size= $element['size'];
152     }
153     $maxlength= 30;
154     if (isset($element['maxlength'])){
155       $maxlength= $element['maxlength'];
156     }
157     $result= "<input class='filter_textfield' id='$tag' name='$tag' type='text' size='$size' maxlength='maxlength' value='".$this->elementValues[$tag]."'>";
158     if (isset($element['autocomplete'])) {
159       $frequency= "0.5";
160       $characters= "1";
161       if (isset($element['autocomplete']['frequency'])) {
162         $frequency= $element['autocomplete']['frequency'];
163       }
164       if (isset($element['autocomplete']['characters'])) {
165         $characters= $element['autocomplete']['characters'];
166       }
167       $result.= "<div id='autocomplete$tag' class='autocomplete'></div>".
168                 "<script type='text/javascript'>".
169                 "new Ajax.Autocompleter('$tag', 'autocomplete$tag', 'autocomplete.php', { minChars: $characters, frequency: $frequency });".
170                 "</script>";
172        $this->autocompleters[$tag]= $element['autocomplete'];
173     }
174     return $result;
175   }
178   function getCheckbox($element)
179   {
180     $tag= $element['tag'];
181     $checked= "";
182     if ($this->elementValues[$tag] == "true") {
183       $checked= " checked";
184     }
186     $result= "<input class='filter_checkbox' name='$tag' type='checkbox' onClick='document.mainform.submit();' value='true'$checked>";
187     return $result;
188   }
191   function getCombobox($element)
192   {
193     $result= "<select name='".$element['tag']."' size='1' onClick='document.mainform.submit();'>";
195     // Fill with presets
196     foreach ($element['value'] as $value) {
197       $selected= "";
198       if ($this->elementValues[$element['tag']] == $value['key']) {
199         $selected= " selected";
200       }
202       // Handle translations
203       $result.= "<option value='".$value['key']."'$selected>"._($value['label'])."</option>";
204     }
206     // Use autocompleter for additional data
207     if (isset($element['autocomplete'])) {
208       $list= $this->getCompletitionList($element['autocomplete'], $element['tag']);
209       foreach ($list as $value) {
210         $selected= "";
211         if ($this->elementValues[$element['tag']] == $value) {
212           $selected= " selected";
213         }
214         $result.= "<option value='$value'$selected>$value</option>";
215       }
216     }
218     $result.= "</select>";
220     return $result;
221   }
224   function getCurrentBase()
225   {
226     if (isset($this->search->base) && (string)$this->search->scope != "auto") {
227       return false;
228     }
230     return $this->base;
231   }
234   function getCurrentScope()
235   {
236     if (isset($this->search->scope) && (string)$this->search->scope != "auto") {
237       return (string)$this->search->scope;
238     }
240     return $this->scope;
241   }
244   function setConverter($field, $hook)
245   {
246     $this->converter[$field]= $hook;
247   }
250   function setObjectStorage($storage)
251   {
252     $this->objectStorage= $storage;    
253   }
256   function setBase($base)
257   {
258     $this->base= $base;
259   }
262   function setCurrentScope($scope)
263   {
264     $this->scope= $scope;
265   }
268   function renderAlphabet($columns= 10)
269   {
270     // Return pre-rendered alphabet if available
271     if ($this->alphabet) {
272       return ($this->alphabet);
273     }
275     $characters= _("*ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
276     $alphabet= "";
277     $c= 0;
279     /* Fill cells with charaters */
280     for ($i= 0, $l= mb_strlen($characters, 'UTF8'); $i<$l; $i++){
281       if ($c == 0){
282         $alphabet.= "<tr>";
283       }
285       $ch = mb_substr($characters, $i, 1, "UTF8");
286       $alphabet.= "<td><a class=\"alphaselect\" href=\"main.php?plug=".
287         validate($_GET['plug'])."&amp;filter=".$ch."\">&nbsp;".$ch."&nbsp;</a></td>";
289       if ($c++ == $columns){
290         $alphabet.= "</tr>";
291         $c= 0;
292       }
293     }
295     /* Fill remaining cells */
296     while ($c++ <= $columns){
297       $alphabet.= "<td>&nbsp;</td>";
298     }
300     /* Save alphabet */
301     $this->alphabet= "<table width='100%'>$alphabet</table>";
303     return ($this->alphabet);
304   }
307   function renderApply()
308   {
309     return ("<input type='submit' name='apply' value='"._("Apply filter")."'>");
310   }
313   function renderScope()
314   {
315     $checked= $this->scope == "sub"?" checked":"";
316     return "<input type='checkbox' name='SCOPE' value='1' onClick='document.mainform.submit();'$checked>&nbsp;"._("Search in subtrees");
317   }
320   function render()
321   {
322     $smarty= get_smarty();
323     $smarty->assign("ALPHABET", $this->renderAlphabet());
324     $smarty->assign("APPLY", $this->renderApply());
325     $smarty->assign("SCOPE", $this->renderScope());
327     // Load template and replace elementsHtml[]
328     foreach ($this->elements as $tag => $element) {
329       $htmlCode= "";
330       switch ($element['type']) {
331         case "textfield":
332           $htmlCode = $this->getTextfield($element);
333           break;
335         case "checkbox":
336           $htmlCode = $this->getCheckbox($element);
337           break;
339         case "combobox":
340           $htmlCode = $this->getCombobox($element);
341           break;
343         default:
344           die ("Unknown element type specified!");
345       }
346       $smarty->assign("$tag", $htmlCode);
347     }
349     // Load template
350     return ("<input type='hidden' name='FILTER_PID' value='".$this->pid."'>".$smarty->fetch(get_template_path($this->xmlData['definition']['template'], true)));
351   }
354   function query()
355   {
356     global $class_mapping;
357     $result= array();
359     // Return empty list if initial is not set
360     if (!$this->initial) {
361       $this->initial= true;
362       return $result;
363     }
365     // Go thru all queries and merge results
366     foreach ($this->query as $query) {
367       if (!isset($query['backend']) || !isset($query['filter']) || !isset($query['attribute'])) {
368         die("No backend specified in search config.");
369       }
371       // Is backend available?
372       $backend= "filter".$query['backend'];
373       if (!isset($class_mapping["$backend"])) {
374         die("Invalid backend specified in search config.");
375       }
377       // Load filter and attributes
378       $filter= $query['filter'];
379       $attributes= $query['attribute'];
381       // Generate final filter
382       foreach ($this->elements as $tag => $element) {
383         if (!isset($element['set']) || !isset($element['unset'])) {
384           continue;
385         }
387         // Handle converters if present
388         if (isset($this->converter[$tag])) {
389           preg_match('/([^:]+)::(.*)$/', $this->converter[$tag], $m);
390           $e_set= call_user_func(array($m[1], $m[2]), preg_replace('/\$/', $this->elementValues[$tag], is_array($element['set'])?"":$element['set']));
391           $e_unset= call_user_func(array($m[1], $m[2]), preg_replace('/\$/', $this->elementValues[$tag], is_array($element['unset'])?"":$element['unset']));
392         } else {
393           $e_set= is_array($element['set'])?"":$element['set'];
394           $e_unset= is_array($element['unset'])?"":$element['unset'];
395         }
397         if ($this->elementValues[$tag] == "") {
398           $e_unset= preg_replace('/\$/', normalizeLdap($this->elementValues[$tag]), $e_unset);
399           $filter= preg_replace("/\\$$tag/", $e_unset, $filter);
400         } else {
401           $e_set= preg_replace('/\$/', normalizeLdap($this->elementValues[$tag]), $e_set);
402           $filter= preg_replace("/\\$$tag/", $e_set, $filter);
403         }
404       }
406       $result= array_merge($result, call_user_func(array($backend, 'query'), $this->base, $this->scope, $filter, $attributes, $this->category, $this->objectStorage));
407     }
408     
409     return ($result);
410   }
413   function isValid()
414   {
415     foreach ($this->elements as $tag => $element) {
416       if (isset($element->regex)){
417         if (!preg_match('/'.(string)$element->regex.'/', $this->elementValues[$tag])){
418           return false;
419         }
420       }
421     }
422     return true;
423   }
426   function update()
427   {
429     /* React on alphabet links if needed */
430     if (isset($_GET['filter'])){
431       $s= mb_substr(validate($_GET['filter']), 0, 1, "UTF8")."*";
432       if ($s == "**"){
433         $s= "*";
434       }
435       foreach ($this->alphabetElements as $tag) {
436         $this->elementValues[$tag]= $s;
437       }
438     }
440     if (isset($_POST['FILTER_PID']) && $_POST['FILTER_PID'] == $this->pid) {
441       // Load post values and adapt filter, base and scope accordingly - but
442       // only if we didn't get a _GET
443       foreach ($this->elements as $tag => $element) {
444         if (isset($_POST[$tag])){
445           $this->elementValues[$tag]= validate($_POST[$tag]);
446         } else {
447           $this->elementValues[$tag]= "";
448         }
449       }
451       // Save scope if needed
452       if ($this->scopeMode == "auto") {
453         $this->scope= isset($_POST['SCOPE'])?"sub":"one";
454       }
455     }
457   }
460   function getCompletitionList($config, $tag, $value="*")
461   {
462     global $class_mapping;
463     $res= array();
465     // Is backend available?
466     $backend= "filter".$config['backend'];
467     if (!isset($class_mapping["$backend"])) {
468       die("Invalid backend specified in search config.");
469     }
471     // Load filter and attributes
472     $filter= $config['filter'];
473     $attributes= $config['attribute'];
474     if (!is_array($attributes)) {
475       $attributes= array($attributes);
476     }
478     // Make filter
479     $filter= preg_replace("/\\$$tag/", normalizeLDAP($value), $filter);
480     if (isset($config['base']) && isset($config['scope']) && isset($config['category'])) {
481       $result= call_user_func(array($backend, 'query'), $config['base'], $config['scope'], $filter, $attributes,
482                            $config["category"], $config["objectStorage"]);
483     } else {
484       $result= call_user_func(array($backend, 'query'), $this->base, $this->scope, $filter, $attributes,
485                            $this->category, $this->objectStorage);
486     }
488     foreach ($result as $entry) {
489       foreach ($attributes as $attribute) {
490         if (is_array($entry[$attribute])) {
491           for ($i= 0; $i<$entry[$attribute]['count']; $i++) {
492             if (mb_stristr($entry[$attribute][$i], $value)) {
493               $res[]= $entry[$attribute][$i];
494             }
495           }
496         } else {
497           $res[]= $entry[$attribute];
498         }
499       }
500     }
502     return $res;
503   }
506   function processAutocomplete()
507   {
508     global $class_mapping;
509     $result= array();
511     // Introduce maximum number of entries
512     $max= 25;
514     foreach ($this->autocompleters as $tag => $config) {
515       if(isset($_POST[$tag])){
516         $result= $this->getCompletitionList($config, $tag, $_POST[$tag]);
517         $result= array_unique($result);
518         asort($result);
520         echo '<ul>';
521         foreach ($result as $entry) {
522           echo '<li>'.$entry.'</li>';
523           if ($max-- == 0) {
524             break;
525           }
526         }
528         echo '</ul>';
529       }
530     }
531   }
536 ?>