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 $searches= array();
27 var $search;
28 var $category= "";
29 var $objectStorage= array();
30 var $base= "";
31 var $scope= "";
32 var $query;
33 var $value= "";
34 var $initial= false;
35 var $scopeMode= "auto";
36 var $converter= null;
37 var $pid;
40 function filter($filename)
41 {
42 global $config;
44 // Load eventually passed filename
45 if (!$this->load($filename)) {
46 die("Cannot parse $filename!");
47 }
49 $this->pid= preg_replace("/[^0-9]/", "", microtime(TRUE));
50 }
53 function load($filename)
54 {
55 $contents = file_get_contents($filename);
56 $this->xmlData= xml::xml2array($contents, 1);
58 if (!isset($this->xmlData['filterdef'])) {
59 return false;
60 }
62 $this->xmlData= $this->xmlData["filterdef"];
64 // Load filter
65 if (isset($this->xmlData['search'])) {
67 // Array conversion
68 if (!is_array($this->xmlData['search'])) {
69 $searches= array($this->xmlData['search']);
70 } else {
71 $searches= $this->xmlData['search'];
72 }
74 // Fix problem with only one defined query.
75 if(isset($this->xmlData['search']['query'])){
76 $this->xmlData['search'] = array($this->xmlData['search']);
77 }
79 /* Store available searches */
80 foreach ($this->xmlData['search'] as $search) {
82 /* Do multi conversation */
83 if (!is_array($search['query'])){
84 $search['query']= array($search['query']);
85 }
87 /* Store search */
88 $this->searches[$search['tag']]= $search;
90 }
91 } else {
92 return false;
93 }
95 // Transfer scope
96 $this->scopeMode= $this->xmlData['definition']['scope'];
97 if ($this->scopeMode == "auto") {
98 $this->scope= "one";
99 } else {
100 $this->scope= $this->scopeMode;
101 }
103 // Transfer initial value
104 if (isset($this->xmlData['definition']['initial']) && $this->xmlData['definition']['initial'] == "true"){
105 $this->initial= true;
106 }
108 // Transfer category
109 if (isset($this->xmlData['definition']['category'])){
110 $this->category= $this->xmlData['definition']['category'];
111 }
113 // Set default search mode
114 $this->setSearch($this->xmlData['definition']['default']);
116 return true;
117 }
120 function setSearch($method)
121 {
122 $patch= null;
123 if (is_array($this->category)) {
124 $categories= $this->category;
125 } else {
126 $categories= array($this->category);
127 }
128 $userfilters= userFilter::getFilter($categories);
130 // User filter selected?
131 if (isset($userfilters[$method])){
132 $usermethod= $method;
133 $patch= $userfilters[$method]['filter'];
134 $method= $userfilters[$method]['parent'];
135 }
137 // Move information
138 if (isset($this->searches[$method])) {
139 $this->query= array_merge($this->searches[$method]['query']);
140 if (!isset($this->query[0])) {
141 $this->query= array($this->query);
142 }
144 // Patch first filter?
145 if ($patch) {
146 $this->query[0]['filter']= $patch;
147 $method= $usermethod;
148 }
150 $this->search= $method;
151 } else {
152 die ("Invalid search module!");
153 }
154 }
157 function getTextfield($tag, $value= "", $element= null)
158 {
159 $size= 30;
160 $maxlength= 30;
161 $result= "<input class='filter_textfield' id='$tag' name='$tag' type='text' size='$size' maxlength='{$maxlength}' value='".$value."'>";
162 if ($element && isset($element['autocomplete'])) {
163 $frequency= "0.5";
164 $characters= "1";
165 if (isset($element['autocomplete']['frequency'])) {
166 $frequency= $element['autocomplete']['frequency'];
167 }
168 if (isset($element['autocomplete']['characters'])) {
169 $characters= $element['autocomplete']['characters'];
170 }
171 $result.= "<div id='autocomplete$tag' class='autocomplete'></div>".
172 "<script type='text/javascript'>".
173 "new Ajax.Autocompleter('$tag', 'autocomplete$tag', 'autocomplete.php', { minChars: $characters, frequency: $frequency });".
174 "</script>";
176 $this->autocompleters[$tag]= $element;
177 }
178 return $result;
179 }
182 function getCurrentBase()
183 {
184 if (isset($this->search->base) && (string)$this->search->scope != "auto") {
185 return false;
186 }
188 return $this->base;
189 }
192 function getCurrentScope()
193 {
194 if (isset($this->search->scope) && (string)$this->search->scope != "auto") {
195 return (string)$this->search->scope;
196 }
198 return $this->scope;
199 }
202 function setConverter($hook)
203 {
204 $this->converter= $hook;
205 }
208 function setObjectStorage($storage)
209 {
210 $this->objectStorage= $storage;
211 }
214 function setBase($base)
215 {
216 $this->base= $base;
217 }
220 function setCurrentScope($scope)
221 {
222 $this->scope= $scope;
223 }
227 function render()
228 {
229 $content= "<table class='filter-wrapper'><tr><td>".$this->renderFilterMenu()."</td><td>";
230 $content.= "<div class='search-filter'>".$this->getTextfield('search_filter', $this->value, $this->searches[$this->search])."</div>".
231 " <button class='search-filter' type='submit' title='"._("Search")."'>".image("images/find.png")."</button></td></tr></table>";
233 // Return meta data
234 return ("<input type='hidden' name='FILTER_PID' value='".$this->pid."'>".$content);
235 }
238 function query()
239 {
240 global $class_mapping;
241 $result= array();
243 // Return empty list if initial is not set
244 if (!$this->initial) {
245 $this->initial= true;
246 return $result;
247 }
249 // Go thru all queries and merge results
250 foreach ($this->query as $query) {
251 if (!isset($query['backend']) || !isset($query['filter']) || !isset($query['attribute'])) {
252 die("No backend specified in search config.");
253 }
255 // Is backend available?
256 $backend= "filter".$query['backend'];
257 if (!isset($class_mapping["$backend"])) {
258 die("Invalid backend specified in search config.");
259 }
261 // Load filter and attributes
262 $filter= $query['filter'];
263 $attributes= $query['attribute'];
265 // Handle converters if present
266 if ($this->converter) {
267 preg_match('/([^:]+)::(.*)$/', $this->converter, $m);
268 $filter= call_user_func(array($m[1], $m[2]), preg_replace('/\$/', $this->value, $filter));
269 }
271 // Do not replace escaped \$ - This is required to be able to search for e.g. windows machines.
272 if ($this->value == "") {
273 $filter= preg_replace("/\\$/", '*', $filter);
274 } else {
275 $filter= preg_replace("/\\$/", "*".normalizeLdap($this->value)."*", $filter);
276 }
278 $result= array_merge($result, call_user_func(array($backend, 'query'), $this->base, $this->scope, $filter, $attributes, $this->category, $this->objectStorage));
279 }
281 return ($result);
282 }
285 function update()
286 {
287 if (isset($_POST['FILTER_PID']) && $_POST['FILTER_PID'] == $this->pid) {
289 // Save input field
290 if (isset($_POST['search_filter'])) {
291 $this->value= validate($_POST['search_filter']);
292 }
294 // Save scope if needed
295 if ($this->scopeMode == "auto" && isset($_POST['act']) && $_POST['act'] == "toggle-subtree") {
296 $this->scope= ($this->scope == "one")?"sub":"one";
297 }
299 // Switch filter?
300 if (is_array($this->category)) {
301 $categories= $this->category;
302 } else {
303 $categories= array($this->category);
304 }
306 if (isset($_POST['act'])) {
307 foreach (array_merge($this->searches, userFilter::getFilter($categories)) as $tag => $cfg) {
308 if ($_POST['act'] == "filter-$tag") {
309 $this->setSearch($tag);
310 break;
311 }
312 }
313 }
314 }
316 }
319 function getCompletitionList($config, $value="*")
320 {
321 global $class_mapping;
322 $res= array();
324 // Load result attributes
325 $attributes= $config['autocomplete']['attribute'];
326 if (!is_array($attributes)) {
327 $attributes= array($attributes);
328 }
330 // Do the query
331 $result= array();
333 // Is backend available?
334 $queries= $config['query'];
335 if (!isset($queries[0])){
336 $queries= array($queries);
337 }
338 foreach ($queries as $query) {
339 $backend= "filter".$query['backend'];
340 if (!isset($class_mapping["$backend"])) {
341 die("Invalid backend specified in search config.");
342 }
343 $filter= preg_replace("/\\$/", "*".normalizeLdap($value)."*", $query['filter']);
345 $result= array_merge($result, call_user_func(array($backend, 'query'), $this->base, $this->scope, $filter, $attributes,
346 $this->category, $this->objectStorage));
347 }
349 foreach ($result as $entry) {
350 foreach ($attributes as $attribute) {
351 if (is_array($entry[$attribute])) {
352 for ($i= 0; $i<$entry[$attribute]['count']; $i++) {
353 if (mb_stristr($entry[$attribute][$i], $value)) {
354 $res[]= $entry[$attribute][$i];
355 }
356 }
357 } else {
358 $res[]= $entry[$attribute];
359 }
360 }
361 }
363 return $res;
364 }
367 function processAutocomplete()
368 {
369 global $class_mapping;
370 $result= array();
372 // Introduce maximum number of entries
373 $max= 25;
375 if(isset($this->searches[$this->search]['autocomplete'])){
376 $result= $this->getCompletitionList($this->searches[$this->search], $_POST['search_filter']);
377 $result= array_unique($result);
378 asort($result);
380 echo '<ul>';
381 foreach ($result as $entry) {
382 echo '<li>'.mark($_POST['search_filter'], $entry).'</li>';
383 if ($max-- == 0) {
384 break;
385 }
386 }
388 echo '</ul>';
389 }
390 }
393 function getObjectBase($dn)
394 {
395 global $config;
396 $base= "";
398 // Try every object storage
399 $storage= $this->objectStorage;
400 if (!is_array($storage)){
401 $storage= array($storage);
402 }
403 foreach ($storage as $location) {
404 $pattern= "/^[^,]+,".preg_quote($location, '/')."/i";
405 $base= preg_replace($pattern, '', $dn);
406 }
408 /* Set to base, if we're not on a correct subtree */
409 if (!isset($config->idepartments[$base])){
410 $base= $config->current['BASE'];
411 }
413 return $base;
414 }
418 function renderFilterMenu()
419 {
420 // Load shortcut
421 $result= "<input type='hidden' name='act' id='filter' value=''><div style='display:none'><input type='submit' name='exec_filter' id='exec_filter' value=''></div>".
422 "<ul class='level1' id='filter-root'><li><a href='#'>".image("images/filter.png").image("images/lists/sort-down.png")."</a>";
424 // Build ul/li list
425 $separator= " style='border-top:1px solid #AAA' ";
426 $result.= "<ul class='level2'>";
428 // Build in filters
429 foreach ($this->searches as $tag => $config) {
430 if ($tag == $this->search) {
431 $result.= "<li><a href='#'>".image("images/checked.png")." "._($config['label'])."</a></li>";
432 } else {
433 $result.= "<li><a href='#' onClick='document.getElementById(\"filter\").value= \"filter-$tag\";mainform.submit();'>".image("images/empty.png")." "._($config['label'])."</a></li>";
434 }
435 }
437 // User defined filters
438 $first= true;
439 if (is_array($this->category)) {
440 $categories= $this->category;
441 } else {
442 $categories= array($this->category);
443 }
444 foreach (userFilter::getFilter($categories) as $tag => $config) {
445 if ($tag == $this->search) {
446 $result.= "<li".($first?$separator:"")."><a href='#'>".image("images/checked.png")." "._($config['description'])."</a></li>";
447 } else {
448 $result.= "<li".($first?$separator:"")."><a href='#' onClick='document.getElementById(\"filter\").value= \"filter-$tag\";mainform.submit();'>".image("images/empty.png")." "._($config['description'])."</a></li>";
449 }
451 $first= false;
452 }
454 // Render scope if set to auto
455 if ($this->scopeMode == "auto") {
456 $result.= "<li$separator><a href='#' onClick='document.getElementById(\"filter\").value= \"toggle-subtree\";mainform.submit();'>".($this->scope=="one"?image("images/empty.png"):image("images/checked.png"))." "._("Search in subtrees")."</a></li>";
457 }
460 // Edit filter menu
461 $result.= "<li$separator><a href='#' onClick='document.getElementById(\"filter\").value= \"config-filter\";mainform.submit();'>".image("images/configure.png")." "._("Edit filters")."...</a></li>";
463 $result.= "</ul>";
465 $script= '<script type="text/javascript" language="JavaScript">var menu2; menu2= new Menu("filter-root", "menu2", configMenu)</script>';
467 return "<div id='filtermenu'>".$result."</li></ul><div>$script";
468 }
471 function getFixedFilters()
472 {
473 return array_keys($this->searches);
474 }
476 }
479 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
480 ?>