Code

Fix encoding of base
[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       uasort($this->elements, 'strlenSort');
131       $this->elements= array_reverse($this->elements);
133     }
135     return true;  
136   }
139   function getTextfield($element)
140   {
141     $tag= $element['tag'];
142     $size= 30;
143     if (isset($element['size'])){
144       $size= $element['size'];
145     }
146     $maxlength= 30;
147     if (isset($element['maxlength'])){
148       $maxlength= $element['maxlength'];
149     }
150     $result= "<input class='filter_textfield' id='$tag' name='$tag' type='text' size='$size' maxlength='maxlength' value='".$this->elementValues[$tag]."'>";
151     if (isset($element['autocomplete'])) {
152       $frequency= "0.5";
153       $characters= "1";
154       if (isset($element['autocomplete']['frequency'])) {
155         $frequency= $element['autocomplete']['frequency'];
156       }
157       if (isset($element['autocomplete']['characters'])) {
158         $characters= $element['autocomplete']['characters'];
159       }
160       $result.= "<div id='autocomplete$tag' class='autocomplete'></div>".
161                 "<script type='text/javascript'>".
162                 "new Ajax.Autocompleter('$tag', 'autocomplete$tag', 'autocomplete.php', { minChars: $characters, frequency: $frequency });".
163                 "</script>";
165        $this->autocompleters[$tag]= $element['autocomplete'];
166     }
167     return $result;
168   }
171   function getCheckbox($element)
172   {
173     $tag= $element['tag'];
174     $checked= "";
175     if ($this->elementValues[$tag] == "true") {
176       $checked= " checked";
177     }
179     $result= "<input class='filter_checkbox' id='$tag' name='$tag' type='checkbox' onClick='document.mainform.submit();' value='true'$checked>";
180     return $result;
181   }
184   function getCombobox($element)
185   {
186     $result= "<select name='".$element['tag']."' size='1' onChange='document.mainform.submit();'>";
188     // Fill with presets
189     foreach ($element['value'] as $value) {
190       $selected= "";
191       if ($this->elementValues[$element['tag']] == $value['key']) {
192         $selected= " selected";
193       }
195       // Handle translations
196       $result.= "<option value='".$value['key']."'$selected>"._($value['label'])."</option>";
197     }
199     // Use autocompleter for additional data
200     if (isset($element['autocomplete'])) {
201       $list= $this->getCompletitionList($element['autocomplete'], $element['tag']);
202       foreach ($list as $value) {
203         $selected= "";
204         if ($this->elementValues[$element['tag']] == $value) {
205           $selected= " selected";
206         }
207         $result.= "<option value='$value'$selected>$value</option>";
208       }
209     }
211     $result.= "</select>";
213     return $result;
214   }
217   function setComboBoxOptions($tag, $options)
218   {
219     if (isset($this->elements[$tag]) && $this->elements[$tag]['type'] == "combobox") {
220       
221       $this->elements[$tag]['value']= array();
222       foreach ($options as $key => $label) {
223         $this->elements[$tag]['value'][]= array('label' => $label, 'key' => $key);
224       }
225     }
226   }
229   function getCurrentBase()
230   {
231     if (isset($this->search->base) && (string)$this->search->scope != "auto") {
232       return false;
233     }
235     return $this->base;
236   }
239   function getCurrentScope()
240   {
241     if (isset($this->search->scope) && (string)$this->search->scope != "auto") {
242       return (string)$this->search->scope;
243     }
245     return $this->scope;
246   }
249   function setConverter($field, $hook)
250   {
251     $this->converter[$field]= $hook;
252   }
255   function setObjectStorage($storage)
256   {
257     $this->objectStorage= $storage;    
258   }
261   function setBase($base)
262   {
263     $this->base= $base;
264   }
267   function setCurrentScope($scope)
268   {
269     $this->scope= $scope;
270   }
273   function renderAlphabet($columns= 10)
274   {
275     // Return pre-rendered alphabet if available
276     if ($this->alphabet) {
277       return ($this->alphabet);
278     }
280     $characters= _("*ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
281     $alphabet= "";
282     $c= 0;
284     /* Fill cells with charaters */
285     for ($i= 0, $l= mb_strlen($characters, 'UTF8'); $i<$l; $i++){
286       if ($c == 0){
287         $alphabet.= "<tr>";
288       }
290       $ch = mb_substr($characters, $i, 1, "UTF8");
291       $alphabet.= "<td><a class=\"alphaselect\" href=\"main.php?plug=".
292         validate($_GET['plug'])."&amp;filter=".$ch."\">&nbsp;".$ch."&nbsp;</a></td>";
294       if ($c++ == $columns){
295         $alphabet.= "</tr>";
296         $c= 0;
297       }
298     }
300     /* Fill remaining cells */
301     while ($c++ <= $columns){
302       $alphabet.= "<td>&nbsp;</td>";
303     }
305     /* Save alphabet */
306     $this->alphabet= "<table width='100%'>$alphabet</table>";
308     return ($this->alphabet);
309   }
312   function renderApply()
313   {
314     return ("<input type='submit' name='apply' value='"._("Apply filter")."'>");
315   }
318   function renderScope()
319   {
320     $checked= $this->scope == "sub"?" checked":"";
321     return "<input type='checkbox' id='SCOPE' name='SCOPE' value='1' onClick='document.mainform.submit();'$checked>&nbsp;<LABEL for='SCOPE'>"._("Search in subtrees")."</LABEL>";
322   }
325   function render()
326   {
327     $smarty= get_smarty();
328     $smarty->assign("ALPHABET", $this->renderAlphabet());
329     $smarty->assign("APPLY", $this->renderApply());
330     $smarty->assign("SCOPE", $this->renderScope());
332     // Load template and replace elementsHtml[]
333     foreach ($this->elements as $tag => $element) {
334       $htmlCode= "";
335       switch ($element['type']) {
336         case "textfield":
337           $htmlCode = $this->getTextfield($element);
338           break;
340         case "checkbox":
341           $htmlCode = $this->getCheckbox($element);
342           break;
344         case "combobox":
345           $htmlCode = $this->getCombobox($element);
346           break;
348         default:
349           die ("Unknown element type specified!");
350       }
351       $smarty->assign("$tag", $htmlCode);
352     }
354     // Try to load template from plugin the folder first...
355     $file = get_template_path($this->xmlData['definition']['template'], true);
357     // ... if this fails, try to load the file from the theme folder.
358     if(!file_exists($file)){
359       $file = get_template_path($this->xmlData['definition']['template']);
360     }
362     // Load template
363     return ("<input type='hidden' name='FILTER_PID' value='".$this->pid."'>".$smarty->fetch($file));
364   }
367   function query()
368   {
369     global $class_mapping;
370     $result= array();
372     // Return empty list if initial is not set
373     if (!$this->initial) {
374       $this->initial= true;
375       return $result;
376     }
378     // Go thru all queries and merge results
379     foreach ($this->query as $query) {
380       if (!isset($query['backend']) || !isset($query['filter']) || !isset($query['attribute'])) {
381         die("No backend specified in search config.");
382       }
384       // Is backend available?
385       $backend= "filter".$query['backend'];
386       if (!isset($class_mapping["$backend"])) {
387         die("Invalid backend specified in search config.");
388       }
390       // Load filter and attributes
391       $filter= $query['filter'];
392       $attributes= $query['attribute'];
394       // Generate final filter
395       foreach ($this->elements as $tag => $element) {
396         if (!isset($element['set']) || !isset($element['unset'])) {
397           continue;
398         }
400         // Handle converters if present
401         if (isset($this->converter[$tag])) {
402           preg_match('/([^:]+)::(.*)$/', $this->converter[$tag], $m);
403           $e_set= call_user_func(array($m[1], $m[2]), preg_replace('/\$/', $this->elementValues[$tag], is_array($element['set'])?"":$element['set']));
404           $e_unset= call_user_func(array($m[1], $m[2]), preg_replace('/\$/', $this->elementValues[$tag], is_array($element['unset'])?"":$element['unset']));
405         } else {
406           $e_set= is_array($element['set'])?"":$element['set'];
407           $e_unset= is_array($element['unset'])?"":$element['unset'];
408         }
410         if ($this->elementValues[$tag] == "") {
411           $e_unset= preg_replace('/[^\\\\]\$/', normalizeLdap($this->elementValues[$tag]), $e_unset);
412           $e_unset= preg_replace('/\\\\\$/','$', $e_unset);
413           $filter= preg_replace("/\\$$tag/", $e_unset, $filter);
414         } else {
415           $e_set= preg_replace('/[^\\\\]\$/', normalizeLdap($this->elementValues[$tag]), $e_set);
416           $e_set= preg_replace('/\\\\\$/','$', $e_set);
417           $filter= preg_replace("/\\$$tag/", $e_set, $filter);
418         }
419       }
421       $result= array_merge($result, call_user_func(array($backend, 'query'), $this->base, $this->scope, $filter, $attributes, $this->category, $this->objectStorage));
422     }
423     
424     return ($result);
425   }
428   function isValid()
429   {
430     foreach ($this->elements as $tag => $element) {
431       if (isset($element->regex)){
432         if (!preg_match('/'.(string)$element->regex.'/', $this->elementValues[$tag])){
433           return false;
434         }
435       }
436     }
437     return true;
438   }
441   function update()
442   {
443     /* React on alphabet links if needed */
444     if (isset($_GET['filter'])){
445       $s= mb_substr(validate($_GET['filter']), 0, 1, "UTF8");
446       foreach ($this->alphabetElements as $tag) {
447         $this->elementValues[$tag]= $s;
448       }
449     }
451     if (isset($_POST['FILTER_PID']) && $_POST['FILTER_PID'] == $this->pid) {
452       // Load post values and adapt filter, base and scope accordingly - but
453       // only if we didn't get a _GET
454       foreach ($this->elements as $tag => $element) {
455         if (isset($_POST[$tag])){
456           $this->elementValues[$tag]= validate($_POST[$tag]);
457         } else {
458           $this->elementValues[$tag]= "";
459         }
460       }
462       // Save scope if needed
463       if ($this->scopeMode == "auto") {
464         $this->scope= isset($_POST['SCOPE'])?"sub":"one";
465       }
466     }
468   }
471   function getCompletitionList($config, $tag, $value="*")
472   {
473     global $class_mapping;
474     $res= array();
476     // Is backend available?
477     $backend= "filter".$config['backend'];
478     if (!isset($class_mapping["$backend"])) {
479       die("Invalid backend specified in search config.");
480     }
482     // Load filter and attributes
483     $filter= $config['filter'];
484     $attributes= $config['attribute'];
485     if (!is_array($attributes)) {
486       $attributes= array($attributes);
487     }
489     // Make filter
490     $filter= preg_replace("/\\$$tag/", normalizeLdap($value), $filter);
491     if (isset($config['base']) && isset($config['scope']) && isset($config['category'])) {
492       $result= call_user_func(array($backend, 'query'), $config['base'], $config['scope'], $filter, $attributes,
493                            $config["category"], $config["objectStorage"]);
494     } else {
495       $result= call_user_func(array($backend, 'query'), $this->base, $this->scope, $filter, $attributes,
496                            $this->category, $this->objectStorage);
497     }
499     foreach ($result as $entry) {
500       foreach ($attributes as $attribute) {
501         if (is_array($entry[$attribute])) {
502           for ($i= 0; $i<$entry[$attribute]['count']; $i++) {
503             if (mb_stristr($entry[$attribute][$i], $value)) {
504               $res[]= $entry[$attribute][$i];
505             }
506           }
507         } else {
508           $res[]= $entry[$attribute];
509         }
510       }
511     }
513     return $res;
514   }
517   function processAutocomplete()
518   {
519     global $class_mapping;
520     $result= array();
522     // Introduce maximum number of entries
523     $max= 25;
525     foreach ($this->autocompleters as $tag => $config) {
526       if(isset($_POST[$tag])){
527         $result= $this->getCompletitionList($config, $tag, $_POST[$tag]);
528         $result= array_unique($result);
529         asort($result);
531         echo '<ul>';
532         foreach ($result as $entry) {
533           echo '<li>'.$entry.'</li>';
534           if ($max-- == 0) {
535             break;
536           }
537         }
539         echo '</ul>';
540       }
541     }
542   }
545   function getObjectBase($dn)
546   {
547     global $config;
548     $base= "";
550     // Try every object storage
551     $storage= $this->objectStorage;
552     if (!is_array($storage)){
553       $storage= array($storage);
554     }
555     foreach ($storage as $location) {
556       $pattern= "/^[^,]+,".preg_quote($location, '/')."/i";
557       $base= preg_replace($pattern, '', $dn);
558     }
560     /* Set to base, if we're not on a correct subtree */
561     if (!isset($config->idepartments[$base])){
562       $base= $config->current['BASE'];
563     }
565     return $base;
566   }
571 // Sort elements for element length to allow proper replacing later on
572 function strlenSort($a, $b) {
573   if (strlen($a['tag']) == strlen($b['tag'])) {
574     return 0;
575   }
576   return (strlen($a['tag']) < strlen($b['tag']) ? -1 : 1);
577
579 ?>