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 // Generate final filter
396 foreach ($this->elements as $tag => $element) {
397 if (!isset($element['set']) || !isset($element['unset'])) {
398 continue;
399 }
401 // Handle converters if present
402 if (isset($this->converter[$tag])) {
403 preg_match('/([^:]+)::(.*)$/', $this->converter[$tag], $m);
404 $e_set= call_user_func(array($m[1], $m[2]), preg_replace('/\$/', $this->elementValues[$tag], is_array($element['set'])?"":$element['set']));
405 $e_unset= call_user_func(array($m[1], $m[2]), preg_replace('/\$/', $this->elementValues[$tag], is_array($element['unset'])?"":$element['unset']));
406 } else {
407 $e_set= is_array($element['set'])?"":$element['set'];
408 $e_unset= is_array($element['unset'])?"":$element['unset'];
409 }
411 // Do not replace escaped \$ - This is required to be able to search for e.g. windows machines.
412 if ($this->elementValues[$tag] == "") {
413 $e_unset= preg_replace('/([^\\\\])\$/', '${1}'.normalizeLdap($this->elementValues[$tag]), $e_unset);
414 $e_unset= preg_replace('/\\\\\$/','$', $e_unset);
415 $filter= preg_replace("/\\$$tag/", $e_unset, $filter);
416 } else {
417 $e_set= preg_replace('/([^\\\\])\$/', '${1}'.normalizeLdap($this->elementValues[$tag]), $e_set);
418 $e_set= preg_replace('/\\\\\$/','$', $e_set);
419 $filter= preg_replace("/\\$$tag/", $e_set, $filter);
420 }
421 }
423 // Now call filter method and merge resulting entries.
424 $result= array_merge($result, call_user_func(array($backend, 'query'),
425 $this, $this->base, $this->scope, $filter, $attributes, $this->category, $this->objectStorage));
426 }
428 return ($result);
429 }
432 function isValid()
433 {
434 foreach ($this->elements as $tag => $element) {
435 if (isset($element->regex)){
436 if (!preg_match('/'.(string)$element->regex.'/', $this->elementValues[$tag])){
437 return false;
438 }
439 }
440 }
441 return true;
442 }
445 function update()
446 {
447 /* React on alphabet links if needed */
448 if (isset($_GET['filter'])){
449 $s= mb_substr(validate($_GET['filter']), 0, 1, "UTF8");
450 foreach ($this->alphabetElements as $tag) {
451 $this->elementValues[$tag]= $s;
452 }
453 }
455 if (isset($_POST['FILTER_PID']) && $_POST['FILTER_PID'] == $this->pid) {
456 // Load post values and adapt filter, base and scope accordingly - but
457 // only if we didn't get a _GET
458 foreach ($this->elements as $tag => $element) {
459 if (isset($_POST[$tag])){
460 $this->elementValues[$tag]= validate($_POST[$tag]);
461 } else {
462 $this->elementValues[$tag]= "";
463 }
464 }
466 // Save scope if needed
467 if ($this->scopeMode == "auto") {
468 $this->scope= isset($_POST['SCOPE'])?"sub":"one";
469 }
470 }
472 }
475 function getCompletitionList($config, $tag, $value="*")
476 {
477 global $class_mapping;
478 $res= array();
480 // Is backend available?
481 $backend= "filter".$config['backend'];
482 if (!isset($class_mapping["$backend"])) {
483 die("Invalid backend specified in search config.");
484 }
486 // Load filter and attributes
487 $filter= $config['filter'];
488 $attributes= $config['attribute'];
489 if (!is_array($attributes)) {
490 $attributes= array($attributes);
491 }
493 // Make filter
494 $filter= preg_replace("/\\$$tag/", normalizeLdap($value), $filter);
495 if (isset($config['base']) && isset($config['scope']) && isset($config['category'])) {
496 $result= call_user_func(array($backend, 'query'), $config['base'], $config['scope'], $filter, $attributes,
497 $config["category"], $config["objectStorage"]);
498 } else {
499 $result= call_user_func(array($backend, 'query'), $this->base, $this->scope, $filter, $attributes,
500 $this->category, $this->objectStorage);
501 }
503 foreach ($result as $entry) {
504 foreach ($attributes as $attribute) {
505 if (is_array($entry[$attribute])) {
506 for ($i= 0; $i<$entry[$attribute]['count']; $i++) {
507 if (mb_stristr($entry[$attribute][$i], $value)) {
508 $res[]= $entry[$attribute][$i];
509 }
510 }
511 } else {
512 $res[]= $entry[$attribute];
513 }
514 }
515 }
517 return $res;
518 }
521 function processAutocomplete()
522 {
523 global $class_mapping;
524 $result= array();
526 // Introduce maximum number of entries
527 $max= 25;
529 foreach ($this->autocompleters as $tag => $config) {
530 if(isset($_POST[$tag])){
531 $result= $this->getCompletitionList($config, $tag, $_POST[$tag]);
532 $result= array_unique($result);
533 asort($result);
535 echo '<ul>';
536 foreach ($result as $entry) {
537 echo '<li>'.mark($_POST[$tag], $entry).'</li>';
538 if ($max-- == 0) {
539 break;
540 }
541 }
543 echo '</ul>';
544 }
545 }
546 }
549 function getObjectBase($dn)
550 {
551 global $config;
552 $base= "";
554 // Try every object storage
555 $storage= $this->objectStorage;
556 if (!is_array($storage)){
557 $storage= array($storage);
558 }
559 foreach ($storage as $location) {
560 $pattern= "/^[^,]+,".preg_quote($location, '/')."/i";
561 $base= preg_replace($pattern, '', $dn);
562 }
564 /* Set to base, if we're not on a correct subtree */
565 if (!isset($config->idepartments[$base])){
566 $base= $config->current['BASE'];
567 }
569 return $base;
570 }
573 }
575 // Sort elements for element length to allow proper replacing later on
576 function strlenSort($a, $b) {
577 if (strlen($a['tag']) == strlen($b['tag'])) {
578 return 0;
579 }
580 return (strlen($a['tag']) < strlen($b['tag']) ? -1 : 1);
581 }
583 ?>