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();
41 function filter($filename)
42 {
43 global $config;
45 // Load eventually passed filename
46 if (!$this->load($filename)) {
47 die("Cannot parse $filename!");
48 }
49 }
52 function load($filename)
53 {
54 $contents = file_get_contents($filename);
55 $this->xmlData= xml::xml2array($contents, 1);
57 if (!isset($this->xmlData['filter'])) {
58 return false;
59 }
61 $this->xmlData= $this->xmlData["filter"];
63 // Load filter
64 if (isset($this->xmlData['search'])) {
65 if (!isset($this->xmlData['search']['query'][0])){
66 $this->xmlData['search']['query']= array($this->xmlData['search']['query']);
67 }
69 // Move information
70 $entry= $this->xmlData['search'];
71 $this->scopeMode= $entry['scope'];
72 if ($entry['scope'] == "auto") {
73 $this->scope= "one";
74 } else {
75 $this->scope= $entry['scope'];
76 }
77 $this->query= $entry['query'];
78 } else {
79 return false;
80 }
82 // Transfer initial value
83 if (isset($this->xmlData['definition']['initial']) && $this->xmlData['definition']['initial'] == "true"){
84 $this->initial= true;
85 }
87 // Transfer category
88 if (isset($this->xmlData['definition']['category'])){
89 $this->category= $this->xmlData['definition']['category'];
90 }
92 // Generate formular data
93 if (isset($this->xmlData['element'])) {
94 if (!isset($this->xmlData['element'][0])){
95 $this->xmlData['element']= array($this->xmlData['element']);
96 }
97 foreach ($this->xmlData['element'] as $element) {
99 // Ignore elements without type
100 if (!isset($element['type']) || !isset($element['tag'])) {
101 next;
102 }
104 $tag= $element['tag'];
106 // Fix arrays
107 if (isset($element['value']) && !isset($element['value'][0])) {
108 $element['value']= array($element['value']);
109 }
111 // Store element for quick access
112 $this->elements[$tag] = $element;
114 // Preset elementValues with default values if exist
115 if (isset($element['default']) && !is_array($element['default'])) {
116 $this->elementValues[$tag] = $element['default'];
117 } else {
118 $this->elementValues[$tag] = "";
119 }
121 // Does this element react on alphabet links?
122 if (isset($element['alphabet']) && $element['alphabet'] == "true") {
123 $this->alphabetElements[]= $tag;
124 }
125 }
127 // Sort elements for element length to allow proper replacing later on
128 function strlenSort($a, $b) {
129 if (strlen($a['tag']) == strlen($b['tag'])) {
130 return 0;
131 }
132 return (strlen($a['tag']) < strlen($b['tag']) ? -1 : 1);
133 }
134 uasort($this->elements, 'strlenSort');
135 $this->elements= array_reverse($this->elements);
137 }
139 return true;
140 }
143 function getTextfield($element)
144 {
145 $tag= $element['tag'];
146 $size= 30;
147 if (isset($element['size'])){
148 $size= $element['size'];
149 }
150 $maxlength= 30;
151 if (isset($element['maxlength'])){
152 $maxlength= $element['maxlength'];
153 }
154 $result= "<input class='filter_textfield' id='$tag' name='$tag' type='text' size='$size' maxlength='maxlength' value='".$this->elementValues[$tag]."'>";
155 if (isset($element['autocomplete'])) {
156 $frequency= "0.5";
157 $characters= "1";
158 if (isset($element['autocomplete']['frequency'])) {
159 $frequency= $element['autocomplete']['frequency'];
160 }
161 if (isset($element['autocomplete']['characters'])) {
162 $characters= $element['autocomplete']['characters'];
163 }
164 $result.= "<div id='autocomplete$tag' class='autocomplete'></div>".
165 "<script type='text/javascript'>".
166 "new Ajax.Autocompleter('$tag', 'autocomplete$tag', 'autocomplete.php', { minChars: $characters, frequency: $frequency });".
167 "</script>";
169 $this->autocompleters[$tag]= $element['autocomplete'];
170 }
171 return $result;
172 }
175 function getCheckbox($element)
176 {
177 $tag= $element['tag'];
178 $checked= "";
179 if ($this->elementValues[$tag] == "true") {
180 $checked= " checked";
181 }
183 $result= "<input class='filter_checkbox' name='$tag' type='checkbox' onClick='document.mainform.submit();' value='true'$checked>";
184 return $result;
185 }
188 function getCombobox($element)
189 {
190 $result= "<select name='".$element['tag']."' size='1' onClick='document.mainform.submit();'>";
192 // Fill with presets
193 foreach ($element['value'] as $value) {
194 $selected= "";
195 if ($this->elementValues[$element['tag']] == $value['key']) {
196 $selected= " selected";
197 }
199 // Handle translations
200 $result.= "<option value='".$value['key']."'$selected>"._($value['label'])."</option>";
201 }
203 // Use autocompleter for additional data
204 if (isset($element['autocomplete'])) {
205 $list= $this->getCompletitionList($element['autocomplete'], $element['tag']);
206 foreach ($list as $value) {
207 $selected= "";
208 if ($this->elementValues[$element['tag']] == $value) {
209 $selected= " selected";
210 }
211 $result.= "<option value='$value'$selected>$value</option>";
212 }
213 }
215 $result.= "</select>";
217 return $result;
218 }
221 function getCurrentBase()
222 {
223 if (isset($this->search->base) && (string)$this->search->scope != "auto") {
224 return false;
225 }
227 return $this->base;
228 }
231 function getCurrentScope()
232 {
233 if (isset($this->search->scope) && (string)$this->search->scope != "auto") {
234 return (string)$this->search->scope;
235 }
237 return $this->scope;
238 }
241 function setConverter($field, $hook)
242 {
243 $this->converter[$field]= $hook;
244 }
247 function setObjectStorage($storage)
248 {
249 $this->objectStorage= $storage;
250 }
253 function setBase($base)
254 {
255 $this->base= $base;
256 }
259 function setCurrentScope($scope)
260 {
261 $this->scope= $scope;
262 }
265 function renderAlphabet($columns= 10)
266 {
267 // Return pre-rendered alphabet if available
268 if ($this->alphabet) {
269 return ($this->alphabet);
270 }
272 $characters= _("*ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
273 $alphabet= "";
274 $c= 0;
276 /* Fill cells with charaters */
277 for ($i= 0, $l= mb_strlen($characters, 'UTF8'); $i<$l; $i++){
278 if ($c == 0){
279 $alphabet.= "<tr>";
280 }
282 $ch = mb_substr($characters, $i, 1, "UTF8");
283 $alphabet.= "<td><a class=\"alphaselect\" href=\"main.php?plug=".
284 validate($_GET['plug'])."&filter=".$ch."\"> ".$ch." </a></td>";
286 if ($c++ == $columns){
287 $alphabet.= "</tr>";
288 $c= 0;
289 }
290 }
292 /* Fill remaining cells */
293 while ($c++ <= $columns){
294 $alphabet.= "<td> </td>";
295 }
297 /* Save alphabet */
298 $this->alphabet= "<table width='100%'>$alphabet</table>";
300 return ($this->alphabet);
301 }
304 function renderApply()
305 {
306 return ("<input type='submit' name='apply' value='"._("Apply filter")."'>");
307 }
310 function renderScope()
311 {
312 $checked= $this->scope == "sub"?" checked":"";
313 return "<input type='checkbox' name='SCOPE' value='1' onClick='document.mainform.submit();'$checked> "._("Search in subtrees");
314 }
317 function render()
318 {
319 $smarty= get_smarty();
320 $smarty->assign("ALPHABET", $this->renderAlphabet());
321 $smarty->assign("APPLY", $this->renderApply());
322 $smarty->assign("SCOPE", $this->renderScope());
324 // Load template and replace elementsHtml[]
325 foreach ($this->elements as $tag => $element) {
326 $htmlCode= "";
327 switch ($element['type']) {
328 case "textfield":
329 $htmlCode = $this->getTextfield($element);
330 break;
332 case "checkbox":
333 $htmlCode = $this->getCheckbox($element);
334 break;
336 case "combobox":
337 $htmlCode = $this->getCombobox($element);
338 break;
340 default:
341 die ("Unknown element type specified!");
342 }
343 $smarty->assign("$tag", $htmlCode);
344 }
346 // Load template
347 return ("<input type='hidden' name='FILTER_LOADED' value='1'>".$smarty->fetch(get_template_path($this->xmlData['definition']['template'], true)));
348 }
351 function query()
352 {
353 global $class_mapping;
354 $result= array();
356 // Return empty list if initial is not set
357 if (!$this->initial) {
358 $this->initial= true;
359 return $result;
360 }
362 // Go thru all queries and merge results
363 foreach ($this->query as $query) {
364 if (!isset($query['backend']) || !isset($query['filter']) || !isset($query['attribute'])) {
365 die("No backend specified in search config.");
366 }
368 // Is backend available?
369 $backend= "filter".$query['backend'];
370 if (!isset($class_mapping["$backend"])) {
371 die("Invalid backend specified in search config.");
372 }
374 // Load filter and attributes
375 $filter= $query['filter'];
376 $attributes= $query['attribute'];
378 // Generate final filter
379 foreach ($this->elements as $tag => $element) {
380 if (!isset($element['set']) || !isset($element['unset'])) {
381 continue;
382 }
384 // Handle converters if present
385 if (isset($this->converter[$tag])) {
386 preg_match('/([^:]+)::(.*)$/', $this->converter[$tag], $m);
387 $e_set= call_user_func(array($m[1], $m[2]), preg_replace('/\$/', $this->elementValues[$tag], is_array($element['set'])?"":$element['set']));
388 $e_unset= call_user_func(array($m[1], $m[2]), preg_replace('/\$/', $this->elementValues[$tag], is_array($element['unset'])?"":$element['unset']));
389 } else {
390 $e_set= is_array($element['set'])?"":$element['set'];
391 $e_unset= is_array($element['unset'])?"":$element['unset'];
392 }
394 if ($this->elementValues[$tag] == "") {
395 $e_unset= preg_replace('/\$/', normalizeLdap($this->elementValues[$tag]), $e_unset);
396 $filter= preg_replace("/\\$$tag/", $e_unset, $filter);
397 } else {
398 $e_set= preg_replace('/\$/', normalizeLdap($this->elementValues[$tag]), $e_set);
399 $filter= preg_replace("/\\$$tag/", $e_set, $filter);
400 }
401 }
403 $result= array_merge($result, call_user_func(array($backend, 'query'), $this->base, $this->scope, $filter, $attributes, $this->category, $this->objectStorage));
404 }
406 return ($result);
407 }
410 function isValid()
411 {
412 foreach ($this->elements as $tag => $element) {
413 if (isset($element->regex)){
414 if (!preg_match('/'.(string)$element->regex.'/', $this->elementValues[$tag])){
415 return false;
416 }
417 }
418 }
419 return true;
420 }
423 function update()
424 {
426 /* React on alphabet links if needed */
427 if (isset($_GET['filter'])){
428 $s= mb_substr(validate($_GET['filter']), 0, 1, "UTF8")."*";
429 if ($s == "**"){
430 $s= "*";
431 }
432 foreach ($this->alphabetElements as $tag) {
433 $this->elementValues[$tag]= $s;
434 }
435 }
437 if (isset($_POST['FILTER_LOADED'])) {
438 // Load post values and adapt filter, base and scope accordingly - but
439 // only if we didn't get a _GET
440 foreach ($this->elements as $tag => $element) {
441 if (isset($_POST[$tag])){
442 $this->elementValues[$tag]= validate($_POST[$tag]);
443 } else {
444 $this->elementValues[$tag]= "";
445 }
446 }
448 // Save scope if needed
449 if ($this->scopeMode == "auto") {
450 $this->scope= isset($_POST['SCOPE'])?"sub":"one";
451 }
452 }
454 }
457 function getCompletitionList($config, $tag, $value="*")
458 {
459 global $class_mapping;
460 $res= array();
462 // Is backend available?
463 $backend= "filter".$config['backend'];
464 if (!isset($class_mapping["$backend"])) {
465 die("Invalid backend specified in search config.");
466 }
468 // Load filter and attributes
469 $filter= $config['filter'];
470 $attributes= $config['attribute'];
471 if (!is_array($attributes)) {
472 $attributes= array($attributes);
473 }
475 // Make filter
476 $filter= preg_replace("/\\$$tag/", normalizeLDAP($value), $filter);
477 if (isset($config['base']) && isset($config['scope']) && isset($config['category'])) {
478 $result= call_user_func(array($backend, 'query'), $config['base'], $config['scope'], $filter, $attributes,
479 $config["category"], $config["objectStorage"]);
480 } else {
481 $result= call_user_func(array($backend, 'query'), $this->base, $this->scope, $filter, $attributes,
482 $this->category, $this->objectStorage);
483 }
485 foreach ($result as $entry) {
486 foreach ($attributes as $attribute) {
487 if (is_array($entry[$attribute])) {
488 for ($i= 0; $i<$entry[$attribute]['count']; $i++) {
489 $res[]= $entry[$attribute][$i];
490 }
491 } else {
492 $res[]= $entry[$attribute];
493 }
494 }
495 }
497 return $res;
498 }
501 function processAutocomplete()
502 {
503 global $class_mapping;
504 $result= array();
506 // Introduce maximum number of entries
507 $max= 25;
509 foreach ($this->autocompleters as $tag => $config) {
510 if(isset($_POST[$tag])){
511 $result= $this->getCompletitionList($config, $tag, $_POST[$tag]);
512 $result= array_unique($result);
513 asort($result);
515 echo '<ul>';
516 foreach ($result as $entry) {
517 echo '<li>'.$entry.'</li>';
518 if ($max-- == 0) {
519 break;
520 }
521 }
523 echo '</ul>';
524 }
525 }
526 }
529 }
531 ?>