3 # FILE: SearchParameterSet.php
5 # Part of the ScoutLib application support library
6 # Copyright 2015 Edward Almasy and Internet Scout Research Group
7 # http://scout.wisc.edu
15 # ---- SETUP / CONFIGURATION ---------------------------------------------
27 # if set data supplied
30 # set internal values from data
31 $this->LoadFromData($Data);
44 if (is_callable($Func))
46 self::$CanonicalFieldFunction = $Func;
50 throw new InvalidArgumentException(
"Invalid function supplied.");
63 if (is_callable($Func))
65 self::$PrintableFieldFunction = $Func;
69 throw new InvalidArgumentException(
"Invalid function supplied.");
79 # ---- SET CONSTRUCTION ---------------------------------------------------
94 # normalize field value if supplied
95 if (($Field !== NULL) && isset(self::$CanonicalFieldFunction))
97 $Field = call_user_func(self::$CanonicalFieldFunction, $Field);
100 # make sure search strings are an array
101 if (!is_array($SearchStrings))
102 { $SearchStrings = array($SearchStrings); }
104 # for each search string
105 foreach ($SearchStrings as $String)
110 # add strings to search values for field
111 $this->SearchStrings[$Field][] = $String;
115 # add strings to keyword search values
116 $this->KeywordSearchStrings[] = $String;
129 # if new value supplied
130 if ($NewValue !== NULL)
133 $NormValue = strtoupper($NewValue);
135 # error out if value appears invalid
136 if (($NormValue !==
"AND") && ($NormValue !==
"OR"))
138 throw new InvalidArgumentException(
"New logic setting"
139 .
" is invalid (".$NewValue.
").");
143 $this->
Logic = $NormValue;
146 # return current logic setting to caller
156 # add subgroup to privilege set
157 $this->Subgroups[] = $Set;
162 # ---- DATA TRANSLATION ---------------------------------------------------
175 function Data($NewValue = NULL)
177 # if new data supplied
178 if ($NewValue !== NULL)
180 # unpack set data and load
181 $this->LoadFromData($NewValue);
184 # serialize current data and return to caller
186 if ($this->
Logic !==
"AND") { $Data[
"Logic"] = $this->Logic; }
187 if (count($this->SearchStrings))
188 { $Data[
"SearchStrings"] = $this->SearchStrings; }
189 if (count($this->KeywordSearchStrings))
191 $Data[
"KeywordSearchStrings"] = $this->KeywordSearchStrings;
193 if (count($this->Subgroups))
195 foreach ($this->Subgroups as $Subgroup)
197 $Data[
"Subgroups"][] = $Subgroup->Data();
200 return serialize($Data);
211 # if new value supplied
212 if ($NewValue !== NULL)
215 $this->SetFromUrlParameters($NewValue);
218 # get existing search parameters as URL parameters
219 $Params = $this->GetAsUrlParameters();
221 # sort parameters by parameter name to normalize result
224 # return parameters to caller
239 # combine values into string
242 foreach ($Params as $Index => $Value)
244 $ParamString .= $Separator.$Index.
"=".urlencode($Value);
248 # return string to caller
264 $TruncateLongWordsTo = 0, $Indent =
"")
266 # define list of phrases used to represent logical operators
267 $OperatorPhrases = array(
270 ">" =>
"is greater than",
271 "<" =>
"is less than",
272 ">=" =>
"is at least",
273 "<=" =>
"is no more than",
278 # set characters used to indicate literal strings
279 $LiteralStart = $IncludeHtml ?
"<i>" :
"\"";
280 $LiteralEnd = $IncludeHtml ?
"</i>" :
"\"";
281 $LiteralBreak = $IncludeHtml ?
"<br>\n" :
"\n";
282 $Indent .= $IncludeHtml ?
" " :
" ";
284 # for each keyword search string
285 $Descriptions = array();
286 foreach ($this->KeywordSearchStrings as $SearchString)
288 # escape search string if appropriate
291 $SearchString = defaulthtmlentities($SearchString);
294 # add string to list of descriptions
295 $Descriptions[] = $LiteralStart.$SearchString.$LiteralEnd;
298 # for each field with search strings
299 foreach ($this->SearchStrings as $FieldId => $SearchStrings)
301 # retrieve field name
302 $FieldName = call_user_func(self::$PrintableFieldFunction, $FieldId);
304 # for each search string
305 foreach ($SearchStrings as $SearchString)
307 # extract operator from search string
308 $MatchResult = preg_match(
"/^([=><!]+)(.+)/",
309 $SearchString, $Matches);
311 # determine operator phrase
312 if (($MatchResult == 1) && isset($OperatorPhrases[$Matches[1]]))
314 $OpPhrase = $OperatorPhrases[$Matches[1]];
315 $SearchString = $Matches[2];
319 $OpPhrase =
"contains";
322 # escape field name and search string if appropriate
325 $FieldName = defaulthtmlentities($FieldName);
326 $SearchString = defaulthtmlentities($SearchString);
329 # assemble field and operator and value into description
330 $Descriptions[] = $FieldName.
" ".$OpPhrase.
" "
331 .$LiteralStart.$SearchString.$LiteralEnd;
336 foreach ($this->Subgroups as $Subgroup)
338 # retrieve description for subgroup
339 $Descriptions[] =
"(".$Subgroup->TextDescription($IncludeHtml,
340 $StartWithBreak, $TruncateLongWordsTo, $Indent).
")";
343 # join descriptions with appropriate conjunction
344 $Descrip = join($LiteralBreak.$Indent.
" ".strtolower($this->
Logic).
" ",
347 # if caller requested that long words be truncated
348 if ($TruncateLongWordsTo > 4)
350 # break description into words
351 $Words = explode(
" ", $Descrip);
355 foreach ($Words as $Word)
357 # if word is longer than specified length
358 if (strlen(strip_tags($Word)) > $TruncateLongWordsTo)
360 # truncate word and add ellipsis
361 $Word = NeatlyTruncateString($Word, $TruncateLongWordsTo - 3);
364 # add word to new description
365 $NewDescrip .=
" ".$Word;
368 # set description to new description
369 $Descrip = $NewDescrip;
372 # return description to caller
378 # ---- BACKWARD COMPATIBILITY ---------------------------------------------
391 # set logic for search group
392 $Group[
"Logic"] = ($this->
Logic ==
"OR")
395 # for each set of search strings
396 foreach ($this->SearchStrings as $Field => $Strings)
398 # get text name of field
399 $FieldName = call_user_func(self::$PrintableFieldFunction, $Field);
402 $Group[
"SearchStrings"][$FieldName] = $Strings;
405 # for each keyword search string
406 foreach ($this->KeywordSearchStrings as $String)
408 # add string to keyword entry in group
409 $Group[
"SearchStrings"][
"XXXKeywordXXX"][] = $String;
416 foreach ($this->Subgroups as $Subgroup)
418 # retrieve legacy array for subgroup
419 $SubLegacy = $Subgroup->GetAsLegacyArray();
421 # add groups from legacy array to our array
422 $Legacy = array_merge($Legacy, $SubLegacy);
425 # set logic for whole array
426 $Legacy[
"Logic"] = $Group[
"Logic"];
428 # return array to caller
434 # ---- PRIVATE INTERFACE -------------------------------------------------
436 private $KeywordSearchStrings = array();
437 private $Logic = self::DEFAULT_LOGIC;
438 private $SearchStrings = array();
439 private $Subgroups = array();
441 static private $CanonicalFieldFunction;
442 static private $PrintableFieldFunction;
443 static private $UrlParameterPrefix =
"F";
455 private function LoadFromData($Serialized)
458 $Data = unserialize($Serialized);
459 if (!is_array($Data))
461 throw new InvalidArgumentException(
"Incoming set data"
462 .
" appears invalid.");
466 $this->
Logic = isset($Data[
"Logic"]) ? $Data[
"Logic"] :
"AND";
468 # load search strings
469 $this->SearchStrings = isset($Data[
"SearchStrings"])
470 ? $Data[
"SearchStrings"] : array();
471 $this->KeywordSearchStrings = isset($Data[
"KeywordSearchStrings"])
472 ? $Data[
"KeywordSearchStrings"] : array();
475 $this->Subgroups = array();
476 if (isset($Data[
"Subgroups"]))
478 foreach ($Data[
"Subgroups"] as $SubgroupData)
490 private function GetAsUrlParameters($SetPrefix =
"")
492 # for each search string group in set
494 foreach ($this->SearchStrings as $FieldId => $Values)
496 # get numeric version of field ID if not already numeric
497 if (!is_numeric($FieldId))
499 $FieldId = call_user_func(self::$CanonicalFieldFunction, $FieldId);
502 # for each search string in group
504 foreach ($Values as $Value)
506 # check for too many search strings for this field
507 if ($ParamSuffix ==
"Z")
509 throw new Exception(
"Maximum search parameter complexity"
510 .
" exceeded: more than 26 search parameters for"
511 .
" field ID ".$FieldId.
".");
514 # add search string to URL
515 $Params[self::$UrlParameterPrefix.$SetPrefix
516 .$FieldId.$ParamSuffix] = $Value;
517 $ParamSuffix = ($ParamSuffix ==
"") ?
"A"
518 : chr(ord($ParamSuffix) + 1);
522 # for each keyword search string
524 foreach ($this->KeywordSearchStrings as $Value)
526 # check for too many keyword search strings
527 if ($ParamSuffix ==
"Z")
529 throw new Exception(
"Maximum search parameter complexity"
530 .
" exceeded: more than 26 keyword search parameters.");
533 # add search string to URL
534 $Params[self::$UrlParameterPrefix.$SetPrefix
535 .self::URL_KEYWORD_INDICATOR.$ParamSuffix] = $Value;
536 $ParamSuffix = ($ParamSuffix ==
"") ?
"A"
537 : chr(ord($ParamSuffix) + 1);
540 # add logic if not default
541 if ($this->
Logic != self::DEFAULT_LOGIC)
543 $Params[self::$UrlParameterPrefix.$SetPrefix
544 .self::URL_LOGIC_INDICATOR] = $this->Logic;
547 # for each search parameter subgroup
549 foreach ($this->Subgroups as $Subgroup)
551 # check for too many subgroups
552 if ($SetLetter ==
"Z")
554 throw new Exception(
"Maximum search parameter complexity"
555 .
" exceeded: more than 24 search parameter subgroups.");
558 # retrieve URL string for subgroup and add it to URL
559 $Params = array_merge($Params, $Subgroup->GetAsUrlParameters(
560 $SetPrefix.$SetLetter));
562 # move to next set letter
563 $SetLetter = ($SetLetter == chr(ord(self::URL_KEYWORD_INDICATOR) - 1))
564 ? chr(ord(self::URL_KEYWORD_INDICATOR) + 1)
565 : chr(ord($SetLetter) + 1);
568 # return constructed URL parameter string to caller
578 private function SetFromUrlParameters($UrlParameters)
580 # if string was passed in
581 if (is_string($UrlParameters))
583 # split string into parameter array
584 $Params = explode(
"&", $UrlParameters);
586 # pare down parameter array to search parameter elements
587 # and strip off search parameter prefix
588 $NewUrlParameters = array();
589 foreach ($Params as $Param)
591 if (strpos($Param, self::$UrlParameterPrefix) === 0)
593 list($Index, $Value) = explode(
"=", $Param);
594 $NewUrlParameters[$Index] = urldecode($Value);
597 $UrlParameters = $NewUrlParameters;
600 # for each search parameter
601 foreach ($UrlParameters as $ParamName => $SearchString)
603 # strip off standard search parameter prefix
604 $ParamName = substr($ParamName, strlen(self::$UrlParameterPrefix));
606 # split parameter into component parts
607 $SplitResult = preg_match(
"/^([".self::URL_KEYWORDFREE_RANGE.
"]*)"
608 .
"([0-9".self::URL_KEYWORD_INDICATOR.
"]+)([A-Z]*)$/",
609 $ParamName, $Matches);
611 # if split was successful
612 if ($SplitResult === 1)
614 # pull components from split pieces
615 $SetPrefix = $Matches[1];
616 $FieldId = $Matches[2];
617 $ParamSuffix = $Matches[3];
619 # if set prefix indicates parameter is part of our set
620 if ($SetPrefix ==
"")
624 case self::URL_LOGIC_INDICATOR:
626 $this->
Logic($SearchString);
629 case self::URL_KEYWORD_INDICATOR:
630 # add string to keyword searches
631 $this->KeywordSearchStrings[] = $SearchString;
635 # add string to searches for appropriate field
636 $this->SearchStrings[$FieldId][] = $SearchString;
642 # add parameter to array for subgroup
643 $SubgroupIndex = $SetPrefix[0];
644 $SubgroupPrefix = (strlen($SetPrefix) > 1)
645 ? substr($SetPrefix, 1) :
"";
646 $SubgroupParamIndex = self::$UrlParameterPrefix
647 .$SubgroupPrefix.$FieldId.$ParamSuffix;
648 $SubgroupParameters[$SubgroupIndex][$SubgroupParamIndex]
654 # if subgroups were found
655 if (isset($SubgroupParameters))
657 # for each identified subgroup
658 foreach ($SubgroupParameters as $SubgroupIndex => $Parameters)
660 # create subgroup and set parameters
662 $Subgroup->SetFromUrlParameters($Parameters);
664 # add subgroup to our set
665 $this->Subgroups[] = $Subgroup;
TextDescription($IncludeHtml=TRUE, $StartWithBreak=TRUE, $TruncateLongWordsTo=0, $Indent="")
Data($NewValue=NULL)
Get/set search parameter set data, in the form of an opaque string.
static SetCanonicalFieldFunction($Func)
Register function used to retrieve a canonical value for a field.
Set of parameters used to perform a search.
AddSet(SearchParameterSet $Set)
Add subgroup of search parameters to set.
UrlParameterString($NewValue=NULL)
UrlParameters($NewValue=NULL)
const URL_KEYWORD_INDICATOR
const URL_KEYWORDFREE_RANGE
Logic($NewValue=NULL)
Get/set logic for set.
static SetPrintableFieldFunction($Func)
Register function used to retrieve a printable value for a field.
AddParameter($SearchStrings, $Field=NULL)
Add search parameter to set.
GetAsLegacyArray()
Retrieve search parameters in legacy array format.
__construct($Data=NULL)
Class constructor, used to create a new set or reload an existing set from previously-constructed dat...
const URL_LOGIC_INDICATOR