FusionDirectory
 All Data Structures Files Functions Variables
class_filter.inc
Go to the documentation of this file.
1 <?php
2 /*
3  This code is part of FusionDirectory (http://www.fusiondirectory.org/)
4  Copyright (C) 2003-2010 Cajus Pollmeier
5  Copyright (C) 2011-2016 FusionDirectory
6 
7  This program is free software; you can redistribute it and/or modify
8  it under the terms of the GNU General Public License as published by
9  the Free Software Foundation; either version 2 of the License, or
10  (at your option) any later version.
11 
12  This program is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  GNU General Public License for more details.
16 
17  You should have received a copy of the GNU General Public License
18  along with this program; if not, write to the Free Software
19  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
20 */
21 
30 class filter
31 {
32  var $xmlData;
33  var $elements = array();
34  var $elementValues = array();
35  var $alphabetElements = array();
36  var $autocompleter = array();
37  var $category = '';
38  var $objectStorage = array();
39  var $base = '';
40  var $scope = '';
41  var $query;
42  var $initial = FALSE;
43  var $scopeMode = 'auto';
44  var $alphabet = NULL;
45  var $converter = array();
46  var $pid;
47  var $headpage;
48 
54  function __construct($filename)
55  {
56  // Load eventually passed filename
57  if (!$this->load($filename)) {
58  die("Cannot parse $filename!");
59  }
60 
61  $this->pid = preg_replace("/[^0-9]/", "", microtime(TRUE));
62  }
63 
64  /*
65  * \brief Load a filter
66  *
67  * \param string $filename
68  */
69  function load($filename)
70  {
71  $contents = file_get_contents($filename);
72  $this->xmlData = xml::xml2array($contents, 1);
73 
74  if (!isset($this->xmlData['filterdef']['search'])) {
75  return FALSE;
76  }
77 
78  $this->xmlData = $this->xmlData['filterdef'];
79 
80  // Load filter
81  if (!isset($this->xmlData['search']['query'][0])) {
82  $this->xmlData['search']['query'] = array($this->xmlData['search']['query']);
83  }
84 
85  // Move information
86  $entry = $this->xmlData['search'];
87  $this->scopeMode = $entry['scope'];
88  $this->scope = ($entry['scope'] == 'auto' ? 'one' : $entry['scope']);
89  $this->query = $entry['query'];
90 
91  // Transfer initial value
92  if (isset($this->xmlData['definition']['initial']) && ($this->xmlData['definition']['initial'] == 'true')) {
93  $this->initial = TRUE;
94  }
95 
96  // Transfer category
97  if (isset($this->xmlData['definition']['category'])) {
98  $this->category = $this->xmlData['definition']['category'];
99  }
100 
101  // Generate formular data
102  if (isset($this->xmlData['element'])) {
103  if (!isset($this->xmlData['element'][0])) {
104  $this->xmlData['element'] = array($this->xmlData['element']);
105  }
106  foreach ($this->xmlData['element'] as $element) {
107  // Ignore elements without type
108  if (!isset($element['type']) || !isset($element['tag'])) {
109  continue;
110  }
111 
112  $tag = $element['tag'];
113 
114  // Fix arrays
115  if (isset($element['value']) && !isset($element['value'][0])) {
116  $element['value'] = array($element['value']);
117  }
118 
119  // Store element for quick access
120  $this->elements[$tag] = $element;
121 
122  // Preset elementValues with default values if exist
123  if (isset($element['default']) && !is_array($element['default'])) {
124  $this->elementValues[$tag] = $element['default'];
125  } else {
126  $this->elementValues[$tag] = '';
127  }
128 
129  // Does this element react on alphabet links?
130  if (isset($element['alphabet']) && ($element['alphabet'] == 'true')) {
131  $this->alphabetElements[] = $tag;
132  }
133  }
134 
135  uasort($this->elements, 'strlenSort');
136  $this->elements = array_reverse($this->elements);
137  }
138 
139  return TRUE;
140  }
141 
147  function getTextfield($element)
148  {
149  $tag = $element['tag'];
150  $size = 30;
151  if (isset($element['size'])) {
152  $size = $element['size'];
153  }
154  $maxlength = '';
155  if (isset($element['maxlength'])) {
156  $maxlength = $element['maxlength'];
157  }
158  $result = '<input class="filter_textfield" id="'.$tag.'" name="'.$tag.'" type="text" size="'.$size.'"'.(empty($maxlength) ? '' : ' maxlength="'.$maxlength.'"').' value="'.$this->elementValues[$tag].'"/>';
159  if (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>";
172 
173  $this->autocompleters[$tag] = $element['autocomplete'];
174  }
175  return $result;
176  }
177 
178 
184  function getCheckbox($element)
185  {
186  $tag = $element['tag'];
187  $checked = "";
188  if ($this->elementValues[$tag] == "true") {
189  $checked = " checked";
190  }
191 
192  $result = "<input class='filter_checkbox' id='$tag' name='$tag' type='checkbox' onClick='document.mainform.submit();' value='true'$checked>";
193  return $result;
194  }
195 
201  function getCombobox($element)
202  {
203  $result = "<select name='".$element['tag']."' size='1' onChange='document.mainform.submit();'>";
204 
205  // Fill with presets
206  foreach ($element['value'] as $value) {
207  $selected = "";
208  if ($this->elementValues[$element['tag']] == $value['key']) {
209  $selected = " selected";
210  }
211 
212  // Handle translations
213  $result .= "<option value='".$value['key']."'$selected>"._($value['label'])."</option>";
214  }
215 
216  // Use autocompleter for additional data
217  if (isset($element['autocomplete'])) {
218  $list = $this->getCompletitionList($element['autocomplete'], $element['tag']);
219  foreach ($list as $value) {
220  $selected = "";
221  if ($this->elementValues[$element['tag']] == $value) {
222  $selected = " selected";
223  }
224  $result .= "<option value='$value'$selected>$value</option>";
225  }
226  }
227 
228  $result .= "</select>";
229 
230  return $result;
231  }
232 
240  function setComboBoxOptions($tag, $options)
241  {
242  if (isset($this->elements[$tag]) && ($this->elements[$tag]['type'] == "combobox")) {
243  $this->elements[$tag]['value'] = array();
244  foreach ($options as $key => $label) {
245  $this->elements[$tag]['value'][] = array('label' => $label, 'key' => $key);
246  }
247  }
248  }
249 
257  function setConverter($field, $hook)
258  {
259  $this->converter[$field] = $hook;
260  }
261 
267  function setObjectStorage($storage)
268  {
269  $this->objectStorage = $storage;
270  }
271 
272 
278  function setBase($base)
279  {
280  $this->base = $base;
281  }
282 
288  function setCurrentScope($scope)
289  {
290  $this->scope = $scope;
291  }
292 
298  function renderAlphabet($columns = 10)
299  {
300  // Return pre-rendered alphabet if available
301  if ($this->alphabet) {
302  return $this->alphabet;
303  }
304 
305  $characters = _("*ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
306  $alphabet = "";
307  $c = 0;
308 
309  /* Fill cells with charaters */
310  for ($i = 0, $l = mb_strlen($characters, 'UTF8'); $i < $l; $i++) {
311  if ($c == 0) {
312  $alphabet .= "<tr>";
313  }
314 
315  $ch = mb_substr($characters, $i, 1, "UTF8");
316  $alphabet .= "<td><a class=\"alphaselect\" href=\"main.php?plug=".
317  validate($_GET['plug'])."&amp;filter=".$ch."\">&nbsp;".$ch."&nbsp;</a></td>";
318 
319  if ($c++ == $columns) {
320  $alphabet .= "</tr>";
321  $c = 0;
322  }
323  }
324 
325  /* Fill remaining cells */
326  while ($c++ <= $columns) {
327  $alphabet .= "<td>&nbsp;</td>";
328  }
329 
330  /* Save alphabet */
331  $this->alphabet = "<table width='100%'>$alphabet</table>";
332 
333  return $this->alphabet;
334  }
335 
341  function renderApply()
342  {
343  return "<input type='submit' name='apply' value='"._("Apply filter")."'>";
344  }
345 
351  function renderScope()
352  {
353  $checked = ($this->scope == 'sub' ? ' checked' : '');
354  return "<input type='checkbox' id='SCOPE' name='SCOPE' value='1' onClick='document.mainform.submit();'$checked>&nbsp;<LABEL for='SCOPE'>"._("Search in subtrees")."</LABEL>";
355  }
356 
360  function render()
361  {
362  /* If template is not filled, we display nothing */
363  if (!isset ($this->xmlData['definition']['template'])) {
364  return "";
365  }
366 
367  $smarty = get_smarty();
368  $smarty->assign("ALPHABET", $this->renderAlphabet());
369  $smarty->assign("APPLY", $this->renderApply());
370  $smarty->assign("SCOPE", $this->renderScope());
371 
372  // Load template and replace elementsHtml[]
373  foreach ($this->elements as $tag => $element) {
374  $htmlCode = "";
375  switch ($element['type']) {
376  case "textfield":
377  $htmlCode = $this->getTextfield($element);
378  break;
379 
380  case "checkbox":
381  $htmlCode = $this->getCheckbox($element);
382  break;
383 
384  case "combobox":
385  $htmlCode = $this->getCombobox($element);
386  break;
387 
388  default:
389  throw new Exception ('Unknown element type specified: '.$element['type'].'!');
390  }
391  $smarty->assign("$tag", $htmlCode);
392  }
393 
394  // Try to load template from plugin the folder first...
395  $file = get_template_path($this->xmlData['definition']['template'], TRUE);
396 
397  // ... if this fails, try to load the file from the theme folder.
398  if (!file_exists($file)) {
399  $file = get_template_path($this->xmlData['definition']['template']);
400  }
401 
402  // Load template
403  return "<input type='hidden' name='FILTER_PID' value='".$this->pid."'/>".$smarty->fetch($file);
404  }
405 
409  function query()
410  {
411  global $class_mapping;
412  $result = array();
413 
414  // Return empty list if initial is not set
415  if (!$this->initial) {
416  $this->initial = TRUE;
417  return $result;
418  }
419 
420  // Go thru all queries and merge results
421  foreach ($this->query as $query) {
422  if (!isset($query['backend']) || !isset($query['filter']) || !isset($query['attribute'])) {
423  die('No backend specified in search config.');
424  }
425 
426  // Is backend available?
427  $backend = 'filter'.$query['backend'];
428  if (!isset($class_mapping["$backend"])) {
429  die('Invalid backend specified in search config.');
430  }
431 
432  // Load filter and attributes
433  $filter = $query['filter'];
434  $attributes = $query['attribute'];
435 
436  if ($attributes === '*') {
437  $attributes = array($attributes);
438  } else {
439  if (!is_array($attributes)) {
440  $attributes = array($attributes);
441  }
442  // ObjectClass is required to check permissions later.
443  if (!in_array('objectClass', $attributes)) {
444  $attributes[] = 'objectClass';
445  }
446  }
447 
448  // Generate final filter
449  foreach ($this->elements as $tag => $element) {
450  if (!isset($element['set']) || !isset($element['unset'])) {
451  continue;
452  }
453 
454  // Handle converters if present
455  if (isset($this->converter[$tag])) {
456  preg_match('/([^:]+)::(.*)$/', $this->converter[$tag], $m);
457  $e_set = call_user_func(array($m[1], $m[2]), preg_replace('/\$/', $this->elementValues[$tag], is_array($element['set']) ? '' : $element['set']));
458  $e_unset = call_user_func(array($m[1], $m[2]), preg_replace('/\$/', $this->elementValues[$tag], is_array($element['unset']) ? '' : $element['unset']));
459  } else {
460  $e_set = (is_array($element['set']) ? '' : $element['set']);
461  $e_unset = (is_array($element['unset']) ? '' : $element['unset']);
462  }
463 
464  // Do not replace escaped \$ - This is required to be able to search for e.g. windows machines.
465  if ($this->elementValues[$tag] == '') {
466  $e_unset = preg_replace('/([^\\\\])\$/', '${1}'.ldap_escape_f($this->elementValues[$tag]), $e_unset);
467  $e_unset = preg_replace('/\\\\\$/', '$', $e_unset);
468  $filter = preg_replace("/\\$$tag/", $e_unset, $filter);
469  } else {
470  $e_set = preg_replace('/([^\\\\])\$/', '${1}'.ldap_escape_f($this->elementValues[$tag]), $e_set);
471  $e_set = preg_replace('/\\\\\$/', '$', $e_set);
472  $filter = preg_replace("/\\$$tag/", $e_set, $filter);
473  }
474  }
475 
476  $objectStorage = $this->objectStorage;
477 
478  if (isset($this->elements['FILTERTEMPLATE']) && $this->elementValues['FILTERTEMPLATE']) {
479  $objectStorage = array_merge($objectStorage, array_map(function ($oc) { return 'ou=templates,'.$oc; }, $objectStorage));
480  }
481 
482  // Now call filter method and merge resulting entries.
483  $result = array_merge($result, call_user_func(array($backend, 'query'),
484  $this, $this->base, $this->scope, $filter, $attributes, $this->category, $objectStorage));
485  }
486 
487  return $result;
488  }
489 
495  function isValid()
496  {
497  foreach ($this->elements as $tag => $element) {
498  if (isset($element->regex)) {
499  if (!preg_match('/'.(string)$element->regex.'/', $this->elementValues[$tag])) {
500  return FALSE;
501  }
502  }
503  }
504  return TRUE;
505  }
506 
510  function update()
511  {
512  /* React on alphabet links if needed */
513  if (isset($_GET['filter'])) {
514  $s = mb_substr(validate($_GET['filter']), 0, 1, "UTF8");
515  foreach ($this->alphabetElements as $tag) {
516  $this->elementValues[$tag] = $s;
517  }
518  }
519 
520  if (isset($_POST['FILTER_PID']) && $_POST['FILTER_PID'] == $this->pid) {
521  // Load post values and adapt filter, base and scope accordingly - but
522  // only if we didn't get a _GET
523  foreach ($this->elements as $tag => $element) {
524  if (isset($_POST[$tag])) {
525  $this->elementValues[$tag] = validate($_POST[$tag]);
526  } else {
527  $this->elementValues[$tag] = "";
528  }
529  }
530 
531  // Save scope if needed
532  if ($this->scopeMode == 'auto') {
533  $this->scope = (isset($_POST['SCOPE']) ? 'sub' : 'one');
534  }
535  }
536 
537  }
538 
548  function getCompletitionList($cfg, $tag, $value = "*")
549  {
550  global $class_mapping;
551  $res = array();
552 
553  // Is backend available?
554  $backend = "filter".$cfg['backend'];
555  if (!isset($class_mapping["$backend"])) {
556  die("Invalid backend specified in search config.");
557  }
558 
559  // Load filter and attributes
560  $filter = $cfg['filter'];
561  $attributes = $cfg['attribute'];
562  if (!is_array($attributes)) {
563  $attributes = array($attributes);
564  }
565 
566  // ObjectClass is required to check permissions later.
567  if (!in_array('objectClass', $attributes)) {
568  $attributes[] = 'objectClass';
569  }
570 
571  // Make filter
572  $filter = preg_replace("/\\$$tag/", ldap_escape_f($value), $filter);
573  if (isset($cfg['base']) && isset($cfg['scope']) && isset($cfg['category'])) {
574  $result = call_user_func(array($backend, 'query'), $this, $cfg['base'], $cfg['scope'], $filter, $attributes,
575  $cfg["category"], $cfg["objectStorage"]);
576  } else {
577  $result = call_user_func(array($backend, 'query'), $this, $this->base, $this->scope, $filter, $attributes,
578  $this->category, $this->objectStorage);
579  }
580 
581  foreach ($result as $entry) {
582  foreach ($attributes as $attribute) {
583  if (is_array($entry[$attribute])) {
584  for ($i = 0; $i < $entry[$attribute]['count']; $i++) {
585  if (mb_stristr($entry[$attribute][$i], $value)) {
586  $res[] = $entry[$attribute][$i];
587  }
588  }
589  } else {
590  $res[] = $entry[$attribute];
591  }
592  }
593  }
594 
595  return $res;
596  }
597 
602  {
603  $result = array();
604 
605  // Introduce maximum number of entries
606  $max = 25;
607 
608  foreach ($this->autocompleters as $tag => $cfg) {
609  if (isset($_POST[$tag])) {
610  $result = $this->getCompletitionList($cfg, $tag, $_POST[$tag]);
611  $result = array_unique($result);
612  asort($result);
613 
614  echo '<ul>';
615  foreach ($result as $entry) {
616  echo '<li>'.mark(htmlentities($_POST[$tag], ENT_COMPAT, 'UTF-8'), htmlentities($entry, ENT_COMPAT, 'UTF-8')).'</li>';
617  if ($max-- == 0) {
618  break;
619  }
620  }
621 
622  echo '</ul>';
623  }
624  }
625  }
626 }
627 
635 function strlenSort($a, $b)
636 {
637  if (strlen($a['tag']) == strlen($b['tag'])) {
638  return 0;
639  }
640  return (strlen($a['tag']) < strlen($b['tag']) ? -1 : 1);
641 }
642 
643 ?>
setConverter($field, $hook)
Set a converter.
processAutocomplete()
Auto complete.
setBase($base)
Set a base.
strlenSort($a, $b)
Sort elements for element length to allow proper replacing later on.
renderApply()
Render apply filter.
This class contains all the function needed to manage filter.
get_template_path($filename= '', $plugin=FALSE, $path= '')
Return themed path for specified base file.
Definition: functions.inc:315
update()
Update.
isValid()
Check if a filter is valid.
setComboBoxOptions($tag, $options)
Set the combobox options.
& get_smarty()
Get global smarty object.
Definition: functions.inc:953
getTextfield($element)
Get the text in the field.
render()
Render.
setObjectStorage($storage)
Set a object storage.
query()
Query.
getCompletitionList($cfg, $tag, $value="*")
Get competition list.
validate($string)
Removes malicious characters from a (POST) string.
Definition: functions.inc:1722
getCheckbox($element)
Get the checkbox.
renderScope()
Render scope.
setCurrentScope($scope)
Set the current scope.
getCombobox($element)
Get a combobox.
__construct($filename)
Create a filter.
renderAlphabet($columns=10)
Render alphabet.
static xml2array($contents, $get_attributes=1, $priority= 'tag')
Transform a xml document to an array.
Definition: class_xml.inc:41