76c794911643ede8025ad024d02e2ab0ce648a87
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") {
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'])."&filter=".$ch."\"> ".$ch." </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> </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> <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 }
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 }
579 }
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 ?>