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 $searches= array();
26 var $xmlSearches= array();
27 var $attributes= array();
28 var $search;
29 var $defaultSearch;
30 var $category= "";
31 var $objectStorage= array();
32 var $base= "";
33 var $scope= "";
34 var $query;
35 var $value= "";
36 var $initial= false;
37 var $scopeMode= "auto";
38 var $converter= null;
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 $xmlData= xml::xml2array($contents, 1);
60 if (!isset($xmlData['filterdef'])) {
61 return false;
62 }
64 $xmlData= $xmlData["filterdef"];
66 // Load filter
67 if (isset($xmlData['search'])) {
69 // Array conversion
70 if (!isset($xmlData['search'][0])) {
71 $searches= array($xmlData['search']);
72 } else {
73 $searches= $xmlData['search'];
74 }
76 /* Store available searches */
77 foreach ($searches as $search) {
79 /* Do multi conversation */
80 if (!isset($search['query'][0])){
81 $search['query']= array($search['query']);
82 }
84 /* Store search */
85 $this->xmlSearches[$search['tag']]= $search;
87 }
88 } else {
89 return false;
90 }
92 // Transfer scope
93 $this->scopeMode= $xmlData['definition']['scope'];
94 if ($this->scopeMode == "auto") {
95 $this->scope= "one";
96 } else {
97 $this->scope= $this->scopeMode;
98 }
100 // Transfer attributes
101 $this->attributes= $xmlData['definition']['attribute'];
102 if (!is_array($this->attributes)) {
103 $this->attributes= array($this->attributes);
104 }
106 // Transfer initial value
107 if (isset($xmlData['definition']['initial']) && $xmlData['definition']['initial'] == "true"){
108 $this->initial= true;
109 }
111 // Transfer category
112 if (isset($xmlData['definition']['category'])){
113 $this->category= $xmlData['definition']['category'];
114 }
115 if (!is_array($this->category)) {
116 $this->category= array($this->category);
117 }
119 // Initialize searches and default search mode
120 $this->defaultSearch= $xmlData['definition']['default'];
121 $this->reloadFilters();
122 $this->setSearch($this->defaultSearch);
124 return true;
125 }
128 function reloadFilters()
129 {
130 $this->searches= array_merge($this->xmlSearches, userFilter::getFilter($this->category));
131 $this->setSearch($this->search);
132 }
135 function setSearch($method= null)
136 {
137 $patch= null;
139 // Maybe our search method has gone?
140 if (!isset($this->searches[$method])) {
141 $method= $this->defaultSearch;
142 }
144 // Try to use it, but bail out if there's no help...
145 if (isset($this->searches[$method])) {
146 $this->query= $this->searches[$method]['query'];
147 $this->search= $method;
148 } else {
149 die ("Invalid search module!");
150 }
151 }
154 function getTextfield($tag, $value= "", $element= null)
155 {
156 $size= 30;
157 $maxlength= 30;
158 $result= "<input class='filter_textfield' placeholder='"._("Search")."...' id='$tag' name='$tag' type='text' size='$size' maxlength='{$maxlength}' value='".$value."'>";
159 if ($element && isset($element['autocomplete'])) {
160 $frequency= "0.5";
161 $characters= "1";
162 if (isset($element['autocomplete']['frequency'])) {
163 $frequency= $element['autocomplete']['frequency'];
164 }
165 if (isset($element['autocomplete']['characters'])) {
166 $characters= $element['autocomplete']['characters'];
167 }
168 $result.= "<div id='autocomplete$tag' class='autocomplete'></div>".
169 "<script type='text/javascript'>".
170 "new Ajax.Autocompleter('$tag', 'autocomplete$tag', 'autocomplete.php', { minChars: $characters, frequency: $frequency });".
171 "</script>";
173 $this->autocompleters[$tag]= $element;
174 }
175 return $result;
176 }
179 function getCurrentBase()
180 {
181 if (isset($this->search->base) && (string)$this->search->scope != "auto") {
182 return false;
183 }
185 return $this->base;
186 }
189 function getCurrentScope()
190 {
191 if (isset($this->search->scope) && (string)$this->search->scope != "auto") {
192 return (string)$this->search->scope;
193 }
195 return $this->scope;
196 }
199 function setConverter($hook)
200 {
201 $this->converter= $hook;
202 }
205 function setObjectStorage($storage)
206 {
207 $this->objectStorage= $storage;
208 }
211 function setBase($base)
212 {
213 $this->base= $base;
214 }
217 function setCurrentScope($scope)
218 {
219 $this->scope= $scope;
220 }
224 function render()
225 {
226 $content= "<table class='filter-wrapper' summary='"._("Search filter")."'><tr><td>".$this->renderFilterMenu()."</td><td>";
227 $content.= "<div class='search-filter'>".$this->getTextfield('search_filter', $this->value, $this->searches[$this->search])."</div>".
228 " <button class='search-filter' type='submit' title='"._("Search")."'>".image("images/find.png")."</button></td></tr></table>";
230 // Return meta data
231 return ("<input type='hidden' name='FILTER_PID' value='".$this->pid."'>".$content);
232 }
235 function query()
236 {
237 global $class_mapping;
238 $result= array();
240 // Return empty list if initial is not set
241 if (!$this->initial) {
242 $this->initial= true;
243 return $result;
244 }
245 // Go thru all queries and merge results
246 foreach ($this->query as $query) {
247 if (!isset($query['backend']) || !isset($query['filter'])) {
248 die("No backend specified in search config.");
249 }
251 // Is backend available?
252 $backend= "filter".$query['backend'];
253 if (!isset($class_mapping["$backend"])) {
254 die("Invalid backend specified in search config.");
255 }
257 // Load filter and attributes
258 $filter= $query['filter'];
260 // Handle converters if present
261 if ($this->converter) {
262 preg_match('/([^:]+)::(.*)$/', $this->converter, $m);
263 if ($this->value == "") {
264 $filter= call_user_func(array($m[1], $m[2]), preg_replace('/\$/', "*", $filter));
265 } else {
266 $filter= call_user_func(array($m[1], $m[2]), preg_replace('/\$/', $this->value, $filter));
267 }
268 }
270 // Do not replace escaped \$ - This is required to be able to search for e.g. windows machines.
271 if ($this->value == "") {
272 $filter= preg_replace("/\\$/", '*', $filter);
273 } else {
274 $filter= preg_replace("/\\$/", "*".normalizeLdap($this->value)."*", $filter);
275 }
277 $result= array_merge($result, call_user_func(array($backend, 'query'), $this->base, $this->scope, $filter, $this->attributes, $this->category, $this->objectStorage));
278 }
280 return ($result);
281 }
284 function update()
285 {
286 if (isset($_POST['FILTER_PID']) && $_POST['FILTER_PID'] == $this->pid) {
287 // Save input field
288 if (isset($_POST['search_filter'])) {
289 $this->value= get_post('search_filter');
290 }
292 // Save scope if needed
293 if ($this->scopeMode == "auto" && isset($_POST['act']) && $_POST['act'] == "toggle-subtree") {
294 $this->scope= ($this->scope == "one")?"sub":"one";
295 }
297 // Switch filter?
298 if (isset($_POST['act'])) {
299 foreach ($this->searches as $tag => $cfg) {
300 if ($_POST['act'] == "filter-$tag") {
301 $this->setSearch($tag);
302 break;
303 }
304 }
305 }
306 }
308 }
311 function getCompletitionList($config, $value="*")
312 {
313 global $class_mapping;
314 $res= array();
316 // Load result attributes
317 $attributes= $config['autocomplete']['attribute'];
318 if (!is_array($attributes)) {
319 $attributes= array($attributes);
320 }
322 // Do the query
323 $result= array();
325 // Is backend available?
326 # FIXME
327 $queries= $config['query'];
328 if (!isset($queries[0])){
329 $queries= array($queries);
330 }
331 foreach ($queries as $query) {
332 $backend= "filter".$query['backend'];
333 if (!isset($class_mapping["$backend"])) {
334 die("Invalid backend specified in search config.");
335 }
336 $filter= preg_replace("/\\$/", "*".normalizeLdap($value)."*", $query['filter']);
338 $result= array_merge($result, call_user_func(array($backend, 'query'), $this->base, $this->scope, $filter, $attributes,
339 $this->category, $this->objectStorage));
340 }
342 foreach ($result as $entry) {
343 foreach ($attributes as $attribute) {
344 if (is_array($entry[$attribute])) {
345 for ($i= 0; $i<$entry[$attribute]['count']; $i++) {
346 if (mb_stristr($entry[$attribute][$i], $value)) {
347 $res[]= $entry[$attribute][$i];
348 }
349 }
350 } else {
351 $res[]= $entry[$attribute];
352 }
353 }
354 }
356 return $res;
357 }
360 function processAutocomplete()
361 {
362 global $class_mapping;
363 $result= array();
365 // Introduce maximum number of entries
366 $max= 25;
368 if(isset($this->searches[$this->search]['autocomplete'])){
369 $result= $this->getCompletitionList($this->searches[$this->search], get_post('search_filter'));
370 $result= array_unique($result);
371 asort($result);
373 echo '<ul>';
374 foreach ($result as $entry) {
375 echo '<li>'.mark(get_post('search_filter'), $entry).'</li>';
376 if ($max-- == 0) {
377 break;
378 }
379 }
381 echo '</ul>';
382 }
383 }
386 function getObjectBase($dn)
387 {
388 global $config;
389 $base= "";
391 // Try every object storage
392 $storage= $this->objectStorage;
393 if (!is_array($storage)){
394 $storage= array($storage);
395 }
396 foreach ($storage as $location) {
397 $pattern= "/^[^,]+,".preg_quote($location, '/')."/i";
398 $base= preg_replace($pattern, '', $dn);
399 }
401 /* Set to base, if we're not on a correct subtree */
402 if (!isset($config->idepartments[$base])){
403 $base= $config->current['BASE'];
404 }
406 return $base;
407 }
411 function renderFilterMenu()
412 {
413 // Load shortcut
414 $result= "<ul class='level1' id='filter-root'><li><a href='#'>".image("images/filter.png").image("images/lists/sort-down.png")."</a>";
416 // Build ul/li list
417 $separator= " style='border-top:1px solid #AAA' ";
418 $result.= "<ul class='level2'>";
420 // Build in filters
421 foreach ($this->xmlSearches as $tag => $config) {
422 $label = htmlentities(_($config['label']),ENT_COMPAT,'UTF-8');
423 if ($tag == $this->search) {
424 $result.= "<li><a href='#'>".image("images/checked.png")." ".$label."</a></li>";
425 } else {
426 $result.= "<li><a href='#' onClick='\$(\"act\").value= \"filter-$tag\";\$(\"exec_act\").click();'>".image("images/empty.png")." ".$label."</a></li>";
427 }
428 }
430 // User defined filters
431 $first= true;
432 foreach (userFilter::getFilter($this->category) as $tag => $config) {
433 if ($tag == $this->search) {
434 $result.= "<li".($first?$separator:"")."><a href='#'>".image("images/checked.png")." "._($config['description'])."</a></li>";
435 } else {
436 $result.= "<li".($first?$separator:"")."><a href='#' onClick='\$(\"act\").value= \"filter-$tag\";\$(\"exec_act\").click();'>".image("images/empty.png")." "._($config['description'])."</a></li>";
437 }
439 $first= false;
440 }
442 // Render scope if set to auto
443 if ($this->scopeMode == "auto") {
444 $result.= "<li$separator><a href='#' onClick='\$(\"act\").value= \"toggle-subtree\";\$(\"exec_act\").click();'>".($this->scope=="one"?image("images/empty.png"):image("images/checked.png"))." "._("Search in subtrees")."</a></li>";
445 }
448 // Edit filter menu
449 $result.= "<li$separator><a href='#' onClick='\$(\"act\").value= \"config-filter\";\$(\"exec_act\").click();'>".image("images/configure.png")." "._("Edit filters")."...</a></li>";
451 $result.= "</ul>";
453 $script= '<script type="text/javascript" language="JavaScript">var menu2; menu2= new Menu("filter-root", "menu2", configMenu)</script>';
455 return "<div id='filtermenu'>".$result."</li></ul></div>$script";
456 }
459 function getFixedFilters()
460 {
461 return array_keys($this->searches);
462 }
464 }
467 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
468 ?>