objectManager: Fix lower-case letter in 'email Text'
[NewAppDB.git] / include / db_filter_ui.php
blob4a13e64bf76c9c2be36ebc73696bef09f71fa28f
1 <?php
3 /**
4 * User interface for the SQL filter classes
6 * Copyright 2008 BitSplash Software LLC
7 * Copyright 2008 Alexander N. Sørnes <alex@thehandofagony.com>
9 */
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 */
21 class FilterInfo
23 private $sColumn;
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;
40 else
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()
71 return $this->aTypes;
74 public static function getOpName($iOpId)
76 switch($iOpId)
78 case FILTER_EQUALS:
79 return 'equal to';
80 case FILTER_LIKE:
81 return 'like';
82 case FILTER_CONTAINS:
83 return 'contains';
84 case FILTER_STARTS_WITH:
85 return 'starts with';
86 case FILTER_ENDS_WITH:
87 return 'ends with';
88 case FILTER_NOT_LIKE:
89 return 'not like';
90 case FILTER_NOT_EQUALS:
91 return 'not equal to';
92 case FILTER_LESS_THAN:
93 return '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;
104 private $oFilterSet;
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('-');
115 $this->sErrors = '';
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];
150 $sId = $iId;
152 $shEditor = "&i{$sColumn}Op$sId={$oFilter->getOperatorId()}";
153 $shEditor .= "&s{$sColumn}Data$sId={$oFilter->getData()}";
155 return $shEditor;
158 public function getHiddenInputTag($iId, Filter $oFilter)
160 $sColumn = $this->escapeChars($oFilter->getColumn());
161 $oColumn = $this->aFilterInfo[$sColumn];
163 $sId = $iId;
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()}\" />";
168 return $shEditor;
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();
178 $iOp = $aTypes[0];
180 if($iId == -1)
182 /* The first entry in the list of choices is the default */
183 $aValues = $oColumn->getValueTypeData();
184 $sData = $aValues[0];
185 } else
187 $sData = $oFilter->getData();
190 $shRet = "<input type=\"hidden\" name=\"i{$sColumn}Op$sId\" value=\"$iOp\" />";
192 if($sData == 'true')
193 $sChecked = ' checked="checked"';
194 else
195 $sChecked = '';
197 $shRet .= "<input value=\"true\" $sChecked name=\"s{$sColumn}Data$sId\" type=\"checkbox\" />";
198 $shRet .= ' '.$oColumn->getDisplayName();
200 return $shRet;
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]);
222 } else
224 $shEditor .= "<select name='i{$sColumn}Op$sId'>";
226 if($iId != -1)
228 $sSel = '';
229 $sText = 'remove';
231 $shEditor .= "<option value='0'$sSel>-- $sText --</option>";
234 foreach($aTypes as $iType)
236 if($oFilter->getOperatorId() == $iType)
237 $sSel = " selected='selected'";
238 else
239 $sSel = '';
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' />";
249 break;
250 case FILTER_VALUES_ENUM:
251 $shEditor .= $this->getEnumEditor($oColumn, $oFilter, $sId);
252 break;
255 return $shEditor;
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\">";
268 if($sData)
269 $shEditor .= "<option value=\"\">-- remove --</option>";
270 else
271 $shEditor .= "<option value=\"\">-- select --</option>";
273 for($i = 0; $i < sizeof($aOptions); $i++)
275 $sOption = $aOptions[$i];
276 $sSelected = '';
277 if($sData == $sOption)
278 $sSelected = ' selected="selected"';
279 $shEditor .= "<option value=\"$sOption\"$sSelected>{$aOptionNames[$i]}</option>";
282 $shEditor .= "</select>";
284 return $shEditor;
287 /* Get filter data formatted to fit in a URL */
288 public function getUrlData()
290 $shEditor = '';
291 $aCounts = array();
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]++;
307 return $shEditor;
310 /* Get a list of hidden input tags to preserve form data */
311 public function getHiddenFormData()
313 $shEditor = '';
314 $aCounts = array();
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]++;
330 return $shEditor;
333 public function getEditor()
335 $shNewItemsEditor = '';
336 $shCurrentItemsEditor = '';
337 $aCounts = array();
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);
350 else
351 $shCurrentItemsEditor .= $this->getItemEditor($aCounts[$sColumn], $oFilter);
352 $shCurrentItemsEditor .= '<br />';
354 $aCounts[$sColumn]++;
357 $shNewItemsEditor .= '<b>Add new filter</b> <i>(You don&#8217;t have to fill out all rows.)</i><br />';
359 /* Show errors, if any */
360 if($this->sErrors)
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);
372 } else
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)
390 $aReturn = array();
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"];
399 if(!$iOp)
400 continue;
402 $oFilter = new Filter($oOption->getColumn(), $iOp, $sData);
404 /* Only show an option as an active filter if it has been changed
405 from the default */
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];
411 if(!$sData)
412 $sData = 'false';
413 if($sData == $sDefault)
414 continue;
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 />';
441 return $aReturn;
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)
455 $iCount ++;
456 $this->AddFilterObject($oNewFilter);
457 if($iCount > MAX_FILTERS)
458 break;
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()
482 $aOptions = array();
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;
498 return $aOptions;
501 public function getWhereClause()
503 return $this->oFilterSet->getWhereClause();
506 public function getTable($sTable, $iLimit = 0)
508 $hResult = $this->oFilterSet->getMatchedItems($sTable, $iLimit);
510 if(!$hResult)
511 return;
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();