4 * User interface for the SQL filter classes
6 * Copyright 2008 BitSplash Software LLC
7 * Copyright 2008 Alexander N. Sørnes <alex@thehandofagony.com>
11 require_once('db_filter.php');
13 define('FILTER_VALUES_NORMAL', 1);
14 define('FILTER_VALUES_ENUM', 2);
15 define('FILTER_VALUES_OPTION_BOOL', 3);
17 define('MAX_FILTERS', 50);
19 /* Info describing an available filter: what column it applies to,
20 and what comparison options are available */
24 private $sDisplayName;
25 private $aTypes; // Available filters for this column
26 private $iValueType; // Normal, enum ...
27 private $aValueTypeData; // List of enums
28 private $aValueTypeDataDisplay; // Optional display names for enums
30 public function FilterInfo($sColumn, $sDisplayName, $aTypes, $iValueType = FILTER_VALUES_NORMAL
, $aValueTypeData = array(), $aValueTypeDisplay = array())
32 $this->sColumn
= $sColumn;
33 $this->sDisplayName
= $sDisplayName;
34 $this->aTypes
= $aTypes;
35 $this->iValueType
= $iValueType;
36 $this->aValueTypeData
= $aValueTypeData;
38 if(sizeof($aValueTypeData) && !sizeof($aValueTypeDisplay))
39 $this->aValueTypeDataDisplay
= $aValueTypeData;
41 $this->aValueTypeDataDisplay
= $aValueTypeDisplay;
44 public function getColumn()
46 return $this->sColumn
;
49 public function getDisplayName()
51 return $this->sDisplayName
;
54 public function getValueType()
56 return $this->iValueType
;
59 public function getValueTypeData()
61 return $this->aValueTypeData
;
64 public function getValueTypeDataDisplay()
66 return $this->aValueTypeDataDisplay
;
69 public function getTypes()
74 public static function getOpName($iOpId)
84 case FILTER_STARTS_WITH
:
86 case FILTER_ENDS_WITH
:
90 case FILTER_NOT_EQUALS
:
91 return 'not equal to';
92 case FILTER_LESS_THAN
:
94 case FILTER_GREATER_THAN
:
95 return 'greater than';
100 /* Class handling tables where the user can filter contents */
101 class FilterInterface
103 private $aFilterInfo;
105 private $aEscapeChars;
106 private $aEscapeCharsWith;
107 private $sErrors; // Used to inform the user about errors (and to give advice)
109 public function FilterInterface($sTableName = '')
111 $this->aFilterInfo
= array();
112 $this->oFilterSet
= new FilterSet(query_escape_string($sTableName));
113 $this->aEscapeChars
= array('.');
114 $this->aEscapeCharsWith
= array('-');
118 public function AddFilterObject(Filter
$oFilter)
120 $this->oFilterSet
->AddFilterObject($oFilter);
123 public function setFilterSet(FilterSet
$oSet)
125 $this->oFilterSet
= $oSet;
128 /* Convenience function to add a filter option */
129 public function AddFilterInfo($sColumn, $sDisplayName, $aTypes, $iValueType = VALUE_TYPE_NORMAL
, $aValueTypeData = array(), $aValueTypeDisplay = array())
131 $this->aFilterInfo
[$sColumn] = new FilterInfo($sColumn, $sDisplayName, $aTypes, $iValueType, $aValueTypeData, $aValueTypeDisplay);
134 /* We can't use some special chars in variable names, such as '.' */
135 public function escapeChars($sIn)
137 return str_replace($this->aEscapeChars
, $this->aEscapeCharsWith
, $sIn);
140 public function unescapeChars($sIn)
142 return str_replace($this->aEscapeWith
, $this->aEscape
, $sIn);
145 public function getUrlElement($iId, Filter
$oFilter)
147 $sColumn = $this->escapeChars($oFilter->getColumn());
148 $oColumn = $this->aFilterInfo
[$sColumn];
152 $shEditor = "&i{$sColumn}Op$sId={$oFilter->getOperatorId()}";
153 $shEditor .= "&s{$sColumn}Data$sId={$oFilter->getData()}";
158 public function getHiddenInputTag($iId, Filter
$oFilter)
160 $sColumn = $this->escapeChars($oFilter->getColumn());
161 $oColumn = $this->aFilterInfo
[$sColumn];
165 $shEditor = "<input type=\"hidden\" name=\"i{$sColumn}Op$sId\" value=\"{$oFilter->getOperatorId()}\">";
166 $shEditor .= "<input type=\"hidden\" name=\"s{$sColumn}Data$sId\" value=\"{$oFilter->getData()}\" />";
171 public function getOptionBoolEditor($iId, Filter
$oFilter)
173 $sColumn = $this->escapeChars($oFilter->getColumn());
174 $oColumn = $this->aFilterInfo
[$oFilter->getColumn()];
175 $sId = ($iId == -1) ?
'' : $iId;
177 $aTypes = $oColumn->getTypes();
182 /* The first entry in the list of choices is the default */
183 $aValues = $oColumn->getValueTypeData();
184 $sData = $aValues[0];
187 $sData = $oFilter->getData();
190 $shRet = "<input type=\"hidden\" name=\"i{$sColumn}Op$sId\" value=\"$iOp\" />";
193 $sChecked = ' checked="checked"';
197 $shRet .= "<input value=\"true\" $sChecked name=\"s{$sColumn}Data$sId\" type=\"checkbox\" />";
198 $shRet .= ' '.$oColumn->getDisplayName();
203 public function getItemEditor($iId, Filter
$oFilter)
205 $sColumn = $this->escapeChars($oFilter->getColumn());
206 $oColumn = $this->aFilterInfo
[$oFilter->getColumn()];
208 $sId = ($iId == -1) ?
'' : $iId;
209 $shEditor = $oColumn->getDisplayName().' ';
211 $aTypes = $oColumn->getTypes();
213 /* It doesn't make sense to show a dropdown menu of choices if there is only one
214 If the filter is already active then there are more than one; one to remove */
215 if($iId == -1 && sizeof($aTypes) == 1)
217 echo "<input type=\"hidden\" name=\"i{$sColumn}Op$sId\" value=\"{$aTypes[0]}\" />";
219 /* Printing 'equal to' sounds weird if it is the only choice */
220 if($aTypes[0] != FILTER_EQUALS
)
221 $shEditor .= $oColumn->getOpName($aTypes[0]);
224 $shEditor .= "<select name='i{$sColumn}Op$sId'>";
231 $shEditor .= "<option value='0'$sSel>-- $sText --</option>";
234 foreach($aTypes as $iType)
236 if($oFilter->getOperatorId() == $iType)
237 $sSel = " selected='selected'";
240 $shEditor .= "<option value='$iType'$sSel>".$oColumn->getOpName($iType).'</option><br />';
242 $shEditor .= '</select> ';
245 switch($oColumn->getValueType())
247 case FILTER_VALUES_NORMAL
:
248 $shEditor .= "<input type='text' value=\"{$oFilter->getData()}\" name='s{$sColumn}Data$sId' size='30' />";
250 case FILTER_VALUES_ENUM
:
251 $shEditor .= $this->getEnumEditor($oColumn, $oFilter, $sId);
258 public function getEnumEditor($oColumn, $oFilter, $sId)
260 $sColumn = $this->escapeChars($oFilter->getColumn());
261 $aOptions = $oColumn->getValueTypeData();
262 $aOptionNames = $oColumn->getValueTypeDataDisplay();
264 $sData = $oFilter->getData();
266 $shEditor = "<select name=\"s{$sColumn}Data$sId\">";
269 $shEditor .= "<option value=\"\">-- remove --</option>";
271 $shEditor .= "<option value=\"\">-- select --</option>";
273 for($i = 0; $i < sizeof($aOptions); $i++
)
275 $sOption = $aOptions[$i];
277 if($sData == $sOption)
278 $sSelected = ' selected="selected"';
279 $shEditor .= "<option value=\"$sOption\"$sSelected>{$aOptionNames[$i]}</option>";
282 $shEditor .= "</select>";
287 /* Get filter data formatted to fit in a URL */
288 public function getUrlData()
293 foreach($this->oFilterSet
->getFilters() as $oFilter)
295 $sColumn = $oFilter->getColumn();
297 if(!array_key_exists($sColumn, $aCounts))
298 $aCounts[$sColumn] = 0;
300 $shEditor .= $this->getUrlElement($aCounts[$sColumn], $oFilter);
302 $shEditor .= '<br />';
304 $aCounts[$sColumn]++
;
310 /* Get a list of hidden input tags to preserve form data */
311 public function getHiddenFormData()
316 foreach($this->oFilterSet
->getFilters() as $oFilter)
318 $sColumn = $oFilter->getColumn();
320 if(!array_key_exists($sColumn, $aCounts))
321 $aCounts[$sColumn] = 0;
323 $shEditor .= $this->getHiddenInputTag($aCounts[$sColumn], $oFilter);
325 $shEditor .= '<br />';
327 $aCounts[$sColumn]++
;
333 public function getEditor()
335 $shNewItemsEditor = '';
336 $shCurrentItemsEditor = '';
339 if(sizeof($this->oFilterSet
->getFilters()))
340 $shCurrentItemsEditor .= '<br /><b>Active filters</b><br />';
341 foreach($this->oFilterSet
->getFilters() as $oFilter)
343 $sColumn = $oFilter->getColumn();
345 if(!array_key_exists($sColumn, $aCounts))
346 $aCounts[$sColumn] = 0;
348 if($oFilter->getOperatorId() == FILTER_OPTION_BOOL
)
349 $shCurrentItemsEditor .= $this->getOptionBoolEditor($aCounts[$sColumn], $oFilter);
351 $shCurrentItemsEditor .= $this->getItemEditor($aCounts[$sColumn], $oFilter);
352 $shCurrentItemsEditor .= '<br />';
354 $aCounts[$sColumn]++
;
357 $shNewItemsEditor .= '<b>Add new filter</b> <i>(You don’t have to fill out all rows.)</i><br />';
359 /* Show errors, if any */
361 $shNewItemsEditor .= "<font color=\"red\">{$this->sErrors}</font>";
363 foreach($this->aFilterInfo
as $oOption)
365 $oDummyFilter = new Filter($oOption->getColumn(), 0, '');
366 $aTypes = $oOption->getTypes();
368 if($oOption->getValueType() == FILTER_VALUES_OPTION_BOOL
)
370 if(!array_key_exists($oOption->getColumn(), $aCounts))
371 $shNewItemsEditor .= $this->getOptionBoolEditor(-1, $oDummyFilter);
374 $shNewItemsEditor .= $this->getItemEditor(-1, $oDummyFilter);
376 $shNewItemsEditor .= '<br />';
379 return $shNewItemsEditor.$shCurrentItemsEditor;
382 public function getFilterInfo()
384 return $this->aFilterInfo
;
387 /* Reads all input related to filters for the given table column */
388 public function readInputForColumn($aClean, FilterInfo
$oOption)
391 $bChangedOption = false;
393 for($i = 0; array_key_exists('i'.$this->escapeChars($oOption->getColumn())."Op$i", $aClean); $i++
)
395 $sColumn = $this->escapeChars($oOption->getColumn());
396 $sData = query_escape_string(getInput("s{$sColumn}Data$i", $aClean));
397 $iOp = $aClean["i{$sColumn}Op$i"];
402 $oFilter = new Filter($oOption->getColumn(), $iOp, $sData);
404 /* Only show an option as an active filter if it has been changed
406 if($oOption->getValueType() == FILTER_VALUES_OPTION_BOOL
)
408 /* The default option is the first entry in the list of choices */
409 $aChoices = $oOption->getValueTypeData();
410 $sDefault = $aChoices[0];
413 if($sData == $sDefault)
415 $bChangedOption = true;
418 $aReturn[] = $oFilter;
421 if(array_key_exists('i'.$this->escapeChars($oOption->getColumn())."Op", $aClean))
423 $sColumn = $this->escapeChars($oOption->getColumn());
424 $i = sizeof($aReturn);
425 $sData = query_escape_string($aClean["s{$sColumn}Data"]);
426 $iOp = $aClean["i{$sColumn}Op"];
429 if($iOp && $sData && ($oOption->getValueType() != FILTER_VALUES_OPTON_BOOL ||
!$bChangedOoption))
431 $oFilter = new Filter($oOption->getColumn(), $iOp, $sData);
432 $aReturn[] = $oFilter;
433 } else if(!$iOp && $sData)
435 /* The user probably meant to add a filter, but forgot to seelect
436 a filter criterion */
437 $this->sErrors
.= 'You need to select a filter criterion from the drop-down list<br />';
444 /* Reads an input array get enabled filters from form data.
445 The given TableFilterSet defines available options */
446 public function readInput($aClean)
448 $iCount = 0; // We set a maximum for how many filters a user can add,
449 // otherwise we may get a too long SQL query
451 foreach($this->getFilterInfo() as $oOption)
453 foreach($this->readInputForColumn($aClean, $oOption) as $oNewFilter)
456 $this->AddFilterObject($oNewFilter);
457 if($iCount > MAX_FILTERS
)
463 public function loadTable($sTableName)
465 $this->oFilterSet
->loadTable($sTableName);
468 public function saveTable($sTableName)
470 $this->oFilterSet
->saveTable($sTableName);
473 public function getFilterCount()
475 return $this->oFilterSet
->getFilterCount();
478 /* Returns an array of options, where the keys are the columns and the members
479 are the settings themselves */
480 public function getOptions()
483 foreach($this->oFilterSet
->getFilters() as $oFilter)
485 if($oFilter->getOperatorId() == FILTER_OPTION_BOOL
)
486 $aOptions[$oFilter->getColumn()] = $oFilter->getData();
488 foreach($this->aFilterInfo
as $oFilterInfo)
490 if($oFilterInfo->getValueType() == FILTER_VALUES_OPTION_BOOL
&&
491 !array_key_exists($oFilterInfo->getColumn(), $aOptions))
493 $aTypes = $oFilterInfo->getTypes();
494 $sDefault = $aTypes[0];
495 $aOptions[$oFilterInfo->getColumn()] = $sDefault;
501 public function getWhereClause()
503 return $this->oFilterSet
->getWhereClause();
506 public function getTable($sTable, $iLimit = 0)
508 $hResult = $this->oFilterSet
->getMatchedItems($sTable, $iLimit);
513 echo 'Selected '.$this->oFilterSet
->getMatchedItemsCount($sTable).' rows<br><br>';
515 $oTable = new Table();
517 while($aRow = mysql_fetch_row($hResult))
519 $oRow = new TableRow();
521 foreach($aRow as $sCell)
523 $oRow->AddTextCell($sCell);
526 $oTable->AddRow($oRow);
529 return $oTable->getString();