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;
40 function filter($filename)
41 {
42 global $config;
44 // Load eventually passed filename
45 if (!$this->load($filename)) {
46 die("Cannot parse $filename!");
47 }
48 }
51 function load($filename)
52 {
53 $contents = file_get_contents($filename);
54 $this->xmlData= xml::xml2array($contents, 1);
56 if (!isset($this->xmlData['filter'])) {
57 return false;
58 }
60 $this->xmlData= $this->xmlData["filter"];
62 // Load filter
63 if (isset($this->xmlData['search'])) {
64 if (!isset($this->xmlData['search']['query'][0])){
65 $this->xmlData['search']['query']= array($this->xmlData['search']['query']);
66 }
68 // Move information
69 $entry= $this->xmlData['search'];
70 $this->scopeMode= $entry['scope'];
71 if ($entry['scope'] == "auto") {
72 $this->scope= "one";
73 } else {
74 $this->scope= $entry['scope'];
75 }
76 $this->query= $entry['query'];
77 } else {
78 return false;
79 }
81 // Transfer initial value
82 if (isset($this->xmlData['definition']['initial']) && $this->xmlData['definition']['initial'] == "true"){
83 $this->initial= true;
84 }
86 // Transfer category
87 if (isset($this->xmlData['definition']['category'])){
88 $this->category= $this->xmlData['definition']['category'];
89 }
91 // Generate formular data
92 if (isset($this->xmlData['element'])) {
93 if (!isset($this->xmlData['element'][0])){
94 $this->xmlData['element']= array($this->xmlData['element']);
95 }
96 foreach ($this->xmlData['element'] as $element) {
98 // Ignore elements without type
99 if (!isset($element['type']) || !isset($element['tag'])) {
100 next;
101 }
103 $tag= $element['tag'];
105 // Fix arrays
106 if (isset($element['value']) && !isset($element['value'][0])) {
107 $element['value']= array($element['value']);
108 }
110 // Store element for quick access
111 $this->elements[$tag] = $element;
113 // Preset elementValues with default values if exist
114 if (isset($element['default']) && !is_array($element['default'])) {
115 $this->elementValues[$tag] = $element['default'];
116 } else {
117 $this->elementValues[$tag] = "";
118 }
120 // Does this element react on alphabet links?
121 if (isset($element['alphabet']) && $element['alphabet'] == "true") {
122 $this->alphabetElements[]= $tag;
123 }
124 }
126 // Sort elements for element length to allow proper replacing later on
127 function strlenSort($a, $b) {
128 if (strlen($a['tag']) == strlen($b['tag'])) {
129 return 0;
130 }
131 return (strlen($a['tag']) < strlen($b['tag']) ? -1 : 1);
132 }
133 uasort($this->elements, 'strlenSort');
134 $this->elements= array_reverse($this->elements);
136 }
138 return true;
139 }
142 function getTextfield($element)
143 {
144 $tag= $element['tag'];
145 $size= 30;
146 if (isset($element['size'])){
147 $size= $element['size'];
148 }
149 $maxlength= 30;
150 if (isset($element['maxlength'])){
151 $maxlength= $element['maxlength'];
152 }
153 $result= "<input class='filter_textfield' id='$tag' name='$tag' type='text' size='$size' maxlength='maxlength' value='".$this->elementValues[$tag]."'>";
154 if (isset($element['autocomplete'])) {
155 $frequency= "0.5";
156 $characters= "1";
157 if (isset($element['autocomplete']['frequency'])) {
158 $frequency= $element['autocomplete']['frequency'];
159 }
160 if (isset($element['autocomplete']['characters'])) {
161 $characters= $element['autocomplete']['characters'];
162 }
163 $result.= "<div id='autocomplete$tag' class='autocomplete'></div>".
164 "<script type='text/javascript'>".
165 "new Ajax.Autocompleter('$tag', 'autocomplete$tag', 'autocomplete.php', { minChars: $characters, frequency: $frequency });".
166 "</script>";
168 $this->autocompleters[$tag]= $element['autocomplete'];
169 }
170 return $result;
171 }
174 function getCheckbox($element)
175 {
176 $tag= $element['tag'];
177 $checked= "";
178 if ($this->elementValues[$tag] == "true") {
179 $checked= " checked";
180 }
182 $result= "<input class='filter_checkbox' name='$tag' type='checkbox' onClick='document.mainform.submit();' value='true'$checked>";
183 return $result;
184 }
187 function getCombobox($element)
188 {
189 $result= "<select name='".$element['tag']."' size='1' onClick='document.mainform.submit();'>";
191 // Fill with presets
192 foreach ($element['value'] as $value) {
193 $selected= "";
194 if ($this->elementValues[$element['tag']] == $value['key']) {
195 $selected= " selected";
196 }
198 // Handle translations
199 $result.= "<option value='".$value['key']."'$selected>"._($value['label'])."</option>";
200 }
202 // Use autocompleter for additional data
203 if (isset($element['autocomplete'])) {
204 $list= $this->getCompletitionList($element['autocomplete'], $element['tag']);
205 foreach ($list as $value) {
206 $selected= "";
207 if ($this->elementValues[$element['tag']] == $value) {
208 $selected= " selected";
209 }
210 $result.= "<option value='$value'$selected>$value</option>";
211 }
212 }
214 $result.= "</select>";
216 return $result;
217 }
220 function getCurrentBase()
221 {
222 if (isset($this->search->base) && (string)$this->search->scope != "auto") {
223 return false;
224 }
226 return $this->base;
227 }
230 function getCurrentScope()
231 {
232 if (isset($this->search->scope) && (string)$this->search->scope != "auto") {
233 return (string)$this->search->scope;
234 }
236 return $this->scope;
237 }
240 function setObjectStorage($storage) {
241 $this->objectStorage= $storage;
242 }
245 function setBase($base) {
246 $this->base= $base;
247 }
250 function setCurrentScope($scope) {
251 $this->scope= $scope;
252 }
255 function renderAlphabet($columns= 10)
256 {
257 // Return pre-rendered alphabet if available
258 if ($this->alphabet) {
259 return ($this->alphabet);
260 }
262 $characters= _("*ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
263 $alphabet= "";
264 $c= 0;
266 /* Fill cells with charaters */
267 for ($i= 0, $l= mb_strlen($characters, 'UTF8'); $i<$l; $i++){
268 if ($c == 0){
269 $alphabet.= "<tr>";
270 }
272 $ch = mb_substr($characters, $i, 1, "UTF8");
273 $alphabet.= "<td><a class=\"alphaselect\" href=\"main.php?plug=".
274 validate($_GET['plug'])."&filter=".$ch."\"> ".$ch." </a></td>";
276 if ($c++ == $columns){
277 $alphabet.= "</tr>";
278 $c= 0;
279 }
280 }
282 /* Fill remaining cells */
283 while ($c++ <= $columns){
284 $alphabet.= "<td> </td>";
285 }
287 /* Save alphabet */
288 $this->alphabet= "<table width='100%'>$alphabet</table>";
290 return ($this->alphabet);
291 }
294 function renderApply()
295 {
296 return ("<input type='submit' name='apply' value='"._("Apply filter")."'>");
297 }
300 function renderScope()
301 {
302 $checked= $this->scope == "sub"?" checked":"";
303 return "<input type='checkbox' name='SCOPE' value='1' onClick='document.mainform.submit();'$checked> "._("Search in subtrees");
304 }
307 function render()
308 {
309 $smarty= get_smarty();
310 $smarty->assign("ALPHABET", $this->renderAlphabet());
311 $smarty->assign("APPLY", $this->renderApply());
312 $smarty->assign("SCOPE", $this->renderScope());
314 // Load template and replace elementsHtml[]
315 foreach ($this->elements as $tag => $element) {
316 $htmlCode= "";
317 switch ($element['type']) {
318 case "textfield":
319 $htmlCode = $this->getTextfield($element);
320 break;
322 case "checkbox":
323 $htmlCode = $this->getCheckbox($element);
324 break;
326 case "combobox":
327 $htmlCode = $this->getCombobox($element);
328 break;
330 default:
331 die ("Unknown element type specified!");
332 }
333 $smarty->assign("$tag", $htmlCode);
334 }
336 // Load template
337 return ("<input type='hidden' name='FILTER_LOADED' value='1'>".$smarty->fetch(get_template_path($this->xmlData['definition']['template'], true)));
338 }
341 function query()
342 {
343 global $class_mapping;
344 $result= array();
346 // Go thru all queries and merge results
347 foreach ($this->query as $query) {
348 if (!isset($query['backend']) || !isset($query['filter']) || !isset($query['attribute'])) {
349 die("No backend specified in search config.");
350 }
352 // Is backend available?
353 $backend= "filter".$query['backend'];
354 if (!isset($class_mapping["$backend"])) {
355 die("Invalid backend specified in search config.");
356 }
358 // Load filter and attributes
359 $filter= $query['filter'];
360 $attributes= $query['attribute'];
362 // Generate final filter
363 foreach ($this->elements as $tag => $element) {
364 if (!isset($element['set']) || !isset($element['unset'])) {
365 continue;
366 }
367 $e_set= is_array($element['set'])?"":$element['set'];
368 $e_unset= is_array($element['unset'])?"":$element['unset'];
370 if ($this->elementValues[$tag] == "") {
371 $e_unset= preg_replace('/\$/', normalizeLdap($this->elementValues[$tag]), $e_unset);
372 $filter= preg_replace("/\\$$tag/", $e_unset, $filter);
373 } else {
374 $e_set= preg_replace('/\$/', normalizeLdap($this->elementValues[$tag]), $e_set);
375 $filter= preg_replace("/\\$$tag/", $e_set, $filter);
376 }
377 }
379 $result= array_merge($result, call_user_func(array($backend, 'query'), $this->base, $this->scope, $filter, $attributes,
380 $this->category, $this->objectStorage));
381 }
384 return ($result);
385 }
388 function isValid()
389 {
390 foreach ($this->elements as $tag => $element) {
391 if (isset($element->regex)){
392 if (!preg_match('/'.(string)$element->regex.'/', $this->elementValues[$tag])){
393 return false;
394 }
395 }
396 }
397 return true;
398 }
401 function update()
402 {
404 /* React on alphabet links if needed */
405 if (isset($_GET['filter'])){
406 $s= mb_substr(validate($_GET['filter']), 0, 1, "UTF8")."*";
407 if ($s == "**"){
408 $s= "*";
409 }
410 foreach ($this->alphabetElements as $tag) {
411 $this->elementValues[$tag]= $s;
412 }
413 }
415 if (isset($_POST['FILTER_LOADED'])) {
416 // Load post values and adapt filter, base and scope accordingly - but
417 // only if we didn't get a _GET
418 foreach ($this->elements as $tag => $element) {
419 if (isset($_POST[$tag])){
420 $this->elementValues[$tag]= validate($_POST[$tag]);
421 } else {
422 $this->elementValues[$tag]= "";
423 }
424 }
426 // Save scope if needed
427 if ($this->scopeMode == "auto") {
428 $this->scope= isset($_POST['SCOPE'])?"sub":"one";
429 }
430 }
432 }
435 function getCompletitionList($config, $tag, $value="*")
436 {
437 global $class_mapping;
438 $res= array();
440 // Is backend available?
441 $backend= "filter".$config['backend'];
442 if (!isset($class_mapping["$backend"])) {
443 die("Invalid backend specified in search config.");
444 }
446 // Load filter and attributes
447 $filter= $config['filter'];
448 $attributes= $config['attribute'];
449 if (!is_array($attributes)) {
450 $attributes= array($attributes);
451 }
453 // Make filter
454 $filter= preg_replace("/\\$$tag/", normalizeLDAP($value), $filter);
455 if (isset($config['base']) && isset($config['scope']) && isset($config['category'])) {
456 $result= call_user_func(array($backend, 'query'), $config['base'], $config['scope'], $filter, $attributes,
457 $config["category"], $config["objectStorage"]);
458 } else {
459 $result= call_user_func(array($backend, 'query'), $this->base, $this->scope, $filter, $attributes,
460 $this->category, $this->objectStorage);
461 }
463 foreach ($result as $entry) {
464 foreach ($attributes as $attribute) {
465 if (is_array($entry[$attribute])) {
466 for ($i= 0; $i<$entry[$attribute]['count']; $i++) {
467 $res[]= $entry[$attribute][$i];
468 }
469 } else {
470 $res[]= $entry[$attribute];
471 }
472 }
473 }
475 return $res;
476 }
479 function processAutocomplete()
480 {
481 global $class_mapping;
482 $result= array();
484 // Introduce maximum number of entries
485 $max= 25;
487 foreach ($this->autocompleters as $tag => $config) {
488 if(isset($_POST[$tag])){
489 $result= $this->getCompletitionList($config, $tag, $_POST[$tag]);
490 $result= array_unique($result);
491 asort($result);
493 echo '<ul>';
494 foreach ($result as $entry) {
495 echo '<li>'.$entry.'</li>';
496 if ($max-- == 0) {
497 break;
498 }
499 }
501 echo '</ul>';
502 }
503 }
504 }
507 }
509 ?>