CWIS Developer Documentation
SearchParameterSet.php
Go to the documentation of this file.
1 <?PHP
2 #
3 # FILE: SearchParameterSet.php
4 #
5 # Part of the ScoutLib application support library
6 # Copyright 2015-2016 Edward Almasy and Internet Scout Research Group
7 # http://scout.wisc.edu
8 #
9 
14 {
15 
16  # ---- SETUP / CONFIGURATION ---------------------------------------------
17  /*@(*/
18 
26  public function __construct($Data = NULL)
27  {
28  # if set data supplied
29  if ($Data !== NULL)
30  {
31  # set internal values from data
32  $this->LoadFromData($Data);
33  }
34  }
35 
40  public function __clone()
41  {
42  foreach ($this->Subgroups as &$Group)
43  {
44  $Group = clone $Group;
45  }
46  }
47 
55  public static function SetCanonicalFieldFunction($Func)
56  {
57  if (is_callable($Func))
58  {
59  self::$CanonicalFieldFunction = $Func;
60  }
61  else
62  {
63  throw new InvalidArgumentException("Invalid function supplied.");
64  }
65  }
66 
74  public static function SetPrintableFieldFunction($Func)
75  {
76  if (is_callable($Func))
77  {
78  self::$PrintableFieldFunction = $Func;
79  }
80  else
81  {
82  throw new InvalidArgumentException("Invalid function supplied.");
83  }
84  }
85 
91  /*@)*/
92  # ---- SET CONSTRUCTION ---------------------------------------------------
93  /*@(*/
94 
105  public function AddParameter($SearchStrings, $Field = NULL)
106  {
107  # normalize field value if supplied
108  $Field = self::NormalizeField($Field);
109 
110  # make sure search strings are an array
111  if (!is_array($SearchStrings))
112  { $SearchStrings = array($SearchStrings); }
113 
114  # for each search string
115  foreach ($SearchStrings as $String)
116  {
117  # if field specified
118  if ($Field !== NULL)
119  {
120  # add strings to search values for field
121  $this->SearchStrings[$Field][] = $String;
122  }
123  else
124  {
125  # add strings to keyword search values
126  $this->KeywordSearchStrings[] = $String;
127  }
128  }
129  }
130 
139  public function RemoveParameter($SearchStrings, $Field = NULL)
140  {
141  # normalize field value if supplied
142  $Field = self::NormalizeField($Field);
143 
144  # if search strings specified
145  if ($SearchStrings != NULL)
146  {
147  # make sure search strings are an array
148  if (!is_array($SearchStrings))
149  { $SearchStrings = array($SearchStrings); }
150 
151  # for each search string
152  foreach ($SearchStrings as $String)
153  {
154  # if field specified
155  if ($Field !== NULL)
156  {
157  # if there are search parameters for this field
158  if (isset($this->SearchStrings[$Field]))
159  {
160  # remove any matching search parameters
161  $NewSearchStrings = array();
162  foreach ($this->SearchStrings[$Field] as $Value)
163  {
164  if ($Value != $String)
165  {
166  $NewSearchStrings[] = $Value;
167  }
168  }
169  if (count($NewSearchStrings))
170  {
171  $this->SearchStrings[$Field] = $NewSearchStrings;
172  }
173  else
174  {
175  unset($this->SearchStrings[$Field]);
176  }
177  }
178  }
179  else
180  {
181  # remove any matching keyword search parameters
182  $NewSearchStrings = array();
183  foreach ($this->KeywordSearchStrings as $Value)
184  {
185  if ($Value != $String)
186  {
187  $NewSearchStrings[] = $Value;
188  }
189  }
190  $this->KeywordSearchStrings = $NewSearchStrings;
191  }
192  }
193  }
194  else
195  {
196  # if field specified
197  if ($Field !== NULL)
198  {
199  # clear any search strings for this field
200  if (isset($this->SearchStrings[$Field]))
201  {
202  unset($this->SearchStrings[$Field]);
203  }
204  }
205  else
206  {
207  # clear all keyword search parameters
208  $this->KeywordSearchStrings = array();
209  }
210  }
211 
212  # for each parameter subgroup
213  $NewSubgroups = array();
214  foreach ($this->Subgroups as $Group)
215  {
216  # remove parameter from subgroup
217  $Group->RemoveParameter($SearchStrings, $Field);
218 
219  # if the subgroup is not empty
220  if ($Group->ParameterCount())
221  {
222  # keep subgroup
223  $NewSubgroups[] = $Group;
224  }
225  }
226  $this->Subgroups = $NewSubgroups;
227  }
228 
236  public function Logic($NewValue = NULL)
237  {
238  # if new value supplied
239  if ($NewValue !== NULL)
240  {
241  # normalize value
242  $NormValue = ($NewValue == SearchEngine::LOGIC_OR)
243  ? "OR"
244  : (($NewValue == SearchEngine::LOGIC_AND)
245  ? "AND"
246  : strtoupper($NewValue));
247 
248  # error out if value appears invalid
249  if (($NormValue !== "AND") && ($NormValue !== "OR"))
250  {
251  throw new InvalidArgumentException("New logic setting"
252  ." is invalid (".$NewValue.").");
253  }
254 
255  # save new setting
256  $this->Logic = $NormValue;
257  }
258 
259  # return current logic setting to caller
260  return $this->Logic;
261  }
262 
271  public function ItemTypes($ItemTypes = FALSE)
272  {
273  if ($ItemTypes !== FALSE)
274  {
275  if (!is_array($ItemTypes))
276  {
277  $ItemTypes = array($ItemTypes);
278  }
279  $this->ItemTypes = $ItemTypes;
280  }
281  return $this->ItemTypes;
282  }
283 
288  public function AddSet(SearchParameterSet $Set)
289  {
290  if ($Set->ParameterCount()==0)
291  {
292  throw new Exception("Attempt to add empty subgroup");
293  }
294 
295  # add subgroup to privilege set
296  $this->Subgroups[] = $Set;
297  }
298 
299 
300  /*@)*/
301  # ---- DATA RETRIEVAL -----------------------------------------------------
302  /*@(*/
303 
308  public function ParameterCount()
309  {
310  $Count = count($this->KeywordSearchStrings);
311  foreach ($this->SearchStrings as $Field => $Strings)
312  {
313  $Count += count($Strings);
314  }
315  foreach ($this->Subgroups as $Group)
316  {
317  $Count += $Group->ParameterCount();
318  }
319  return $Count;
320  }
321 
329  public function GetSearchStrings($IncludeSubgroups = FALSE)
330  {
331  $SearchStrings = $this->SearchStrings;
332  if ($IncludeSubgroups)
333  {
334  foreach ($this->Subgroups as $Group)
335  {
336  $SubStrings = $Group->GetSearchStrings(TRUE);
337  foreach ($SubStrings as $Field => $Strings)
338  {
339  if (isset($SearchStrings[$Field]))
340  {
341  $SearchStrings[$Field] = array_merge(
342  $SearchStrings[$Field], $Strings);
343  }
344  else
345  {
346  $SearchStrings[$Field] = $Strings;
347  }
348  }
349  }
350  }
351  return $SearchStrings;
352  }
353 
362  public function GetSearchStringsForField($Field, $IncludeSubgroups = TRUE)
363  {
364  # normalize field value
365  $Field = self::NormalizeField($Field);
366 
367  # start with our string values
368  $Strings = isset($this->SearchStrings[$Field])
369  ? $this->SearchStrings[$Field] : array();
370 
371  # if strings from subgroups should also be returned
372  if ($IncludeSubgroups)
373  {
374  # for each subgroup
375  foreach ($this->Subgroups as $Group)
376  {
377  # add any strings from that subgroup
378  $Strings = array_merge($Strings,
379  $Group->GetSearchStringsForField($Field));
380  }
381  }
382 
383  # return all strings found to caller
384  return $Strings;
385  }
386 
391  public function GetKeywordSearchStrings()
392  {
393  return $this->KeywordSearchStrings;
394  }
395 
400  public function GetSubgroups()
401  {
402  return $this->Subgroups;
403  }
404 
409  public function GetFields()
410  {
411  # retrieve our fields
412  $Fields = array_keys($this->SearchStrings);
413 
414  # for each subgroup
415  foreach ($this->Subgroups as $Group)
416  {
417  # add fields from subgroup to the list
418  $Fields = array_merge($Fields, $Group->GetFields());
419  }
420 
421  # filter out duplicates and sort to ensure consistency
422  $Fields = array_unique($Fields);
423  sort($Fields);
424 
425  # return list of field identifiers to caller
426  return $Fields;
427  }
428 
429 
430  /*@)*/
431  # ---- DATA TRANSLATION ---------------------------------------------------
432  /*@(*/
433 
444  public function Data($NewValue = NULL)
445  {
446  # if new data supplied
447  if ($NewValue !== NULL)
448  {
449  # unpack set data and load
450  $this->LoadFromData($NewValue);
451  }
452 
453  # serialize current data and return to caller
454  $Data = array();
455  if ($this->Logic !== "AND") { $Data["Logic"] = $this->Logic; }
456  if (count($this->SearchStrings))
457  { $Data["SearchStrings"] = $this->SearchStrings; }
458  if (count($this->KeywordSearchStrings))
459  {
460  $Data["KeywordSearchStrings"] = $this->KeywordSearchStrings;
461  }
462  if (count($this->Subgroups))
463  {
464  foreach ($this->Subgroups as $Subgroup)
465  {
466  $Data["Subgroups"][] = $Subgroup->Data();
467  }
468  }
469  return serialize($Data);
470  }
471 
478  public function UrlParameters($NewValue = NULL)
479  {
480  # if new value supplied
481  if ($NewValue !== NULL)
482  {
483  # set new parameters
484  $this->SetFromUrlParameters($NewValue);
485  }
486 
487  # get existing search parameters as URL parameters
488  $Params = $this->GetAsUrlParameters();
489 
490  # sort parameters by parameter name to normalize result
491  ksort($Params);
492 
493  # return parameters to caller
494  return $Params;
495  }
496 
503  public function UrlParameterString($NewValue = NULL)
504  {
505  # get/set parameters
506  $Params = $this->UrlParameters($NewValue);
507 
508  # combine values into string
509  $ParamString = "";
510  $Separator = "";
511  foreach ($Params as $Index => $Value)
512  {
513  $ParamString .= $Separator.$Index."=".urlencode($Value);
514  $Separator = "&";
515  }
516 
517  # return string to caller
518  return $ParamString;
519  }
520 
532  public function TextDescription($IncludeHtml = TRUE, $StartWithBreak = TRUE,
533  $TruncateLongWordsTo = 0, $Indent = "")
534  {
535  # define list of phrases used to represent logical operators
536  $OperatorPhrases = array(
537  "=" => "is",
538  "==" => "is",
539  ">" => "is greater than",
540  "<" => "is less than",
541  ">=" => "is at least",
542  "<=" => "is no more than",
543  "!" => "is not",
544  "!=" => "is not",
545  "^" => "begins with",
546  "$" => "ends with",
547  "@" => "was last modified on or after",
548  "@>" => "was last modified after",
549  "@>=" => "was last modified on or after",
550  "@<" => "was last modified before",
551  "@<=" => "was last modified on or before",
552  );
553  $AgoOperatorPhrases = array(
554  "@>" => "was last modified more than",
555  "@>=" => "was last modified at least or more than",
556  "@<" => "was last modified less than",
557  "@<=" => "was last modified at most or less than",
558  );
559 
560  # set characters used to indicate literal strings
561  $LiteralStart = $IncludeHtml ? "<i>" : "\"";
562  $LiteralEnd = $IncludeHtml ? "</i>" : "\"";
563  $LiteralBreak = $IncludeHtml ? "<br>\n" : "\n";
564  $Indent .= $IncludeHtml ? "&nbsp;&nbsp;&nbsp;&nbsp;" : " ";
565 
566  # for each keyword search string
567  $Descriptions = array();
568  foreach ($this->KeywordSearchStrings as $SearchString)
569  {
570  # escape search string if appropriate
571  if ($IncludeHtml)
572  {
573  $SearchString = defaulthtmlentities($SearchString);
574  }
575 
576  # add string to list of descriptions
577  $Descriptions[] = $LiteralStart.$SearchString.$LiteralEnd;
578  }
579 
580  # for each field with search strings
581  foreach ($this->SearchStrings as $FieldId => $SearchStrings)
582  {
583  # retrieve field name
584  $FieldName = call_user_func(self::$PrintableFieldFunction, $FieldId);
585 
586  # for each search string
587  foreach ($SearchStrings as $SearchString)
588  {
589  # extract operator from search string
590  $MatchResult = preg_match('/^([=><!^$@]+)(.+)/',
591  $SearchString, $Matches);
592 
593  # determine operator phrase
594  if (($MatchResult == 1) && isset($OperatorPhrases[$Matches[1]]))
595  {
596  if (isset($AgoOperatorPhrases[$Matches[1]])
597  && strstr($SearchString, "ago"))
598  {
599  $OpPhrase = $AgoOperatorPhrases[$Matches[1]];
600  }
601  else
602  {
603  $OpPhrase = $OperatorPhrases[$Matches[1]];
604  }
605  $SearchString = $Matches[2];
606  }
607  else
608  {
609  $OpPhrase = "contains";
610  }
611 
612  # escape field name and search string if appropriate
613  if ($IncludeHtml)
614  {
615  $FieldName = defaulthtmlentities($FieldName);
616  $SearchString = defaulthtmlentities($SearchString);
617  }
618 
619  # assemble field and operator and value into description
620  $Descriptions[] = $FieldName." ".$OpPhrase." "
621  .$LiteralStart.$SearchString.$LiteralEnd;
622  }
623  }
624 
625  # for each subgroup
626  foreach ($this->Subgroups as $Subgroup)
627  {
628  # retrieve description for subgroup
629  if ($Subgroup->ParameterCount() == 1)
630  {
631  $Descriptions[] = $Subgroup->TextDescription($IncludeHtml,
632  $StartWithBreak, $TruncateLongWordsTo, $Indent);
633  }
634  elseif ($Subgroup->ParameterCount() > 1)
635  {
636  $Descriptions[] = "(".$Subgroup->TextDescription($IncludeHtml,
637  $StartWithBreak, $TruncateLongWordsTo, $Indent).")";
638  }
639  }
640 
641  # join descriptions with appropriate conjunction
642  $Descrip = join($LiteralBreak.$Indent." ".strtolower($this->Logic)." ",
643  $Descriptions);
644 
645  # if caller requested that long words be truncated
646  if ($TruncateLongWordsTo > 4)
647  {
648  # break description into words
649  $Words = explode(" ", $Descrip);
650 
651  # for each word
652  $NewDescrip = "";
653  foreach ($Words as $Word)
654  {
655  # if word is longer than specified length
656  if (strlen(strip_tags($Word)) > $TruncateLongWordsTo)
657  {
658  # truncate word and add ellipsis
659  $Word = NeatlyTruncateString($Word, $TruncateLongWordsTo - 3);
660  }
661 
662  # add word to new description
663  $NewDescrip .= " ".$Word;
664  }
665 
666  # set description to new description
667  $Descrip = $NewDescrip;
668  }
669 
670  # return description to caller
671  return $Descrip;
672  }
673 
674 
675  /*@)*/
676  # ---- UTILITY METHODS ----------------------------------------------------
677  /*@(*/
678 
684  public function ReplaceSearchString($Pattern, $Replacement)
685  {
686  # modify our fielded search strings
687  foreach ($this->SearchStrings as $Field => $Strings)
688  {
689  $this->SearchStrings[$Field] =
690  preg_replace($Pattern, $Replacement, $Strings);
691  }
692 
693  # modify our keyword search strings
694  if (count($this->KeywordSearchStrings))
695  {
696  $this->KeywordSearchStrings =
697  preg_replace($Pattern, $Replacement, $this->KeywordSearchStrings);
698  }
699 
700  # modify any subgroups
701  foreach ($this->Subgroups as $Group)
702  {
703  $Group->ReplaceSearchString($Pattern, $Replacement);
704  }
705  }
706 
707 
708  /*@)*/
709  # ---- BACKWARD COMPATIBILITY ---------------------------------------------
710  /*@(*/
711 
720  public function GetAsLegacyArray()
721  {
722  $Legacy = array();
723 
724  $Group = $this->ConvertToLegacyGroup();
725  if (count($Group))
726  {
727  $Legacy["MAIN"] = $Group;
728  }
729 
730  # for each subgroup
731  foreach ($this->Subgroups as $Subgroup)
732  {
733  # skip empty search groups
734  if (count($Subgroup->SearchStrings)==0)
735  {
736  continue;
737  }
738 
739  $SubLegacy = $Subgroup->ConvertToLegacyGroup();
740 
741  # give an index based on the FieldId of the first
742  # element in the SearchStrings
743  $FieldId = call_user_func(
744  self::$CanonicalFieldFunction,
745  current(array_keys($SubLegacy["SearchStrings"])) );
746 
747  # add groups from legacy array to our array
748  if (!isset($Legacy[$FieldId]))
749  {
750  $Legacy[$FieldId] = $SubLegacy;
751  }
752  else
753  {
754  $Num = count($Legacy[$FieldId]);
755  $Legacy[$FieldId."-".$Num] = $SubLegacy;
756  }
757 
758  if (count($Subgroup->Subgroups))
759  {
760  throw new Exception(
761  "Attempt to convert SearchParameterSet containing nested subgroups "
762  ."to legacy format");
763  }
764  }
765 
766  # return array to caller
767  return $Legacy;
768  }
769 
774  public function SetFromLegacyArray($SearchGroups)
775  {
776  # clear current settings
777  $this->KeywordSearchStrings = array();
778  $this->SearchStrings = array();
779  $this->Subgroups = array();
780 
781  # iterate over legacy search groups
782  foreach ($SearchGroups as $GroupId => $SearchGroup)
783  {
784  if ($GroupId == "MAIN")
785  {
786  # add terms from the main search group to ourself
787  $this->LoadFromLegacyGroup($SearchGroup);
788  }
789  else
790  {
791  # create subgroups for other groups
792  $Subgroup = new SearchParameterSet();
793  $Subgroup->LoadFromLegacyGroup($SearchGroup);
794 
795  # add any non-empty groups
796  if ($Subgroup->ParameterCount())
797  {
798  $this->AddSet($Subgroup);
799  }
800  }
801  }
802  }
803 
808  public function SetFromLegacyUrl($ParameterString)
809  {
810  # clear current settings
811  $this->KeywordSearchStrings = array();
812  $this->SearchStrings = array();
813  $this->Subgroups = array();
814 
815  # extact array of parameters from passed string
816  $GetVars = ParseQueryString($ParameterString);
817 
818  # iterate over the provided parameters
819  foreach ($GetVars as $Key => $Val)
820  {
821  # if this param gives search information
822  if (preg_match("/^([FGH])(K|[0-9]+)$/", $Key, $Matches))
823  {
824  # extract what kind of search it was which field
825  $Type = $Matches[1];
826  $FieldId = $Matches[2];
827 
828  # for 'contains' searches
829  if ($Type == "F")
830  {
831  # add this to our search strings
832  $this->AddParameter( $Val,
833  ($FieldId == "K" ? NULL : $FieldId) );
834  }
835  else
836  {
837  # otherwise, create a subgroup for this parameter
838  $Subgroup = new SearchParameterSet();
839 
840  # set logic based on the search type
841  $Subgroup->Logic($Type=="H" ? "AND" : "OR");
842 
843  # extract the values and add them to a subgroup
844  $Values = explode("-", $Val);
845  $Subgroup->AddParameter(
846  self::TranslateLegacySearchValues($FieldId, $Values),
847  $FieldId );
848 
849  # if subgroup was non-empty, slurp it up
850  if ($Subgroup->ParameterCount())
851  {
852  $this->AddSet($Subgroup);
853  }
854  }
855  }
856  }
857  }
858 
864  public static function IsLegacyUrl($ParameterString)
865  {
866  $QueryVars = ParseQueryString($ParameterString);
867 
868  return (array_key_exists("Q", $QueryVars) &&
869  $QueryVars["Q"] == "Y") ? TRUE : FALSE ;
870  }
871 
877  public static function ConvertLegacyUrl($ParameterString)
878  {
879  $SearchParams = new SearchParameterSet();
880  $SearchParams->SetFromLegacyUrl($ParameterString);
881 
882  return $SearchParams->UrlParameterString();
883  }
884 
891  public static function SetLegacyUrlTranslationFunction($Func)
892  {
893  if (is_callable($Func))
894  {
895  self::$LegacyUrlTranslationFunction = $Func;
896  }
897  else
898  {
899  throw new InvalidArgumentException("Invalid function supplied.");
900  }
901  }
902 
909  public static function TranslateLegacySearchValues($Field, $Values)
910  {
911  if (($Field !== NULL) && isset(self::$LegacyUrlTranslationFunction))
912  {
913  $Values = call_user_func(self::$LegacyUrlTranslationFunction,
914  $Field, $Values);
915  }
916  return $Values;
917  }
918 
919  /*@)*/
920  # ---- PRIVATE INTERFACE -------------------------------------------------
921 
922  private $KeywordSearchStrings = array();
923  private $ItemTypes = NULL;
924  private $Logic = self::DEFAULT_LOGIC;
925  private $SearchStrings = array();
926  private $Subgroups = array();
927 
928  static private $CanonicalFieldFunction;
929  static private $PrintableFieldFunction;
930  static private $LegacyUrlTranslationFunction;
931  static private $UrlParameterPrefix = "F";
932 
933  const DEFAULT_LOGIC = "AND";
934  const URL_KEYWORDFREE_RANGE = "A-JL-Z";
936  const URL_LOGIC_INDICATOR = "00";
937 
943  private function LoadFromData($Serialized)
944  {
945  # unpack new data
946  $Data = unserialize($Serialized);
947  if (!is_array($Data))
948  {
949  throw new InvalidArgumentException("Incoming set data"
950  ." appears invalid.");
951  }
952 
953  # load logic
954  $this->Logic = isset($Data["Logic"]) ? $Data["Logic"] : "AND";
955 
956  # load search strings
957  $this->SearchStrings = isset($Data["SearchStrings"])
958  ? $Data["SearchStrings"] : array();
959  $this->KeywordSearchStrings = isset($Data["KeywordSearchStrings"])
960  ? $Data["KeywordSearchStrings"] : array();
961 
962  # load any subgroups
963  $this->Subgroups = array();
964  if (isset($Data["Subgroups"]))
965  {
966  foreach ($Data["Subgroups"] as $SubgroupData)
967  {
968  $this->Subgroups[] = new SearchParameterSet($SubgroupData);
969  }
970  }
971  }
972 
977  private function ConvertToLegacyGroup()
978  {
979  $Group = array();
980  # for each set of search strings
981  foreach ($this->SearchStrings as $Field => $Strings)
982  {
983  # get text name of field
984  $FieldName = call_user_func(self::$PrintableFieldFunction, $Field);
985 
986  # add set to group
987  $Group["SearchStrings"][$FieldName] = $Strings;
988  }
989 
990  # for each keyword search string
991  foreach ($this->KeywordSearchStrings as $String)
992  {
993  # add string to keyword entry in group
994  $Group["SearchStrings"]["XXXKeywordXXX"][] = $String;
995  }
996 
997  # if we had any search terms
998  if (count($Group))
999  {
1000  # smash single-value arrays to a scalar
1001  foreach ($Group["SearchStrings"] as &$Tgt)
1002  {
1003  if (count($Tgt) == 1)
1004  {
1005  $Tgt = current($Tgt);
1006  }
1007  }
1008 
1009  # set logic for search group
1010  $Group["Logic"] = ($this->Logic == "OR")
1012  }
1013 
1014  return $Group;
1015  }
1016 
1021  private function LoadFromLegacyGroup($Group)
1022  {
1023  # set logic appropriately
1024  $this->Logic(
1025  ($Group["Logic"] == SearchEngine::LOGIC_OR) ?
1026  "OR" : "AND" );
1027 
1028  # if this group had no search strings, we're done
1029  if (!isset($Group["SearchStrings"]))
1030  {
1031  return;
1032  }
1033 
1034  # otherwise, load the search strings
1035  foreach ($Group["SearchStrings"] as $Field => $Params)
1036  {
1037  # skip empty groups
1038  if (count($Params)==0)
1039  {
1040  continue;
1041  }
1042 
1043  $this->AddParameter( $Params,
1044  ($Field == "XXXKeywordXXX" ? NULL : $Field) );
1045 
1046  }
1047  }
1048 
1054  private function GetAsUrlParameters($SetPrefix = "")
1055  {
1056  # for each search string group in set
1057  $Params = array();
1058  foreach ($this->SearchStrings as $FieldId => $Values)
1059  {
1060  # get numeric version of field ID if not already numeric
1061  if (!is_numeric($FieldId))
1062  {
1063  $FieldId = call_user_func(self::$CanonicalFieldFunction, $FieldId);
1064  }
1065 
1066  # for each search string in group
1067  $ParamSuffix = "";
1068  foreach ($Values as $Value)
1069  {
1070  # check for too many search strings for this field
1071  if ($ParamSuffix == "Z")
1072  {
1073  throw new Exception("Maximum search parameter complexity"
1074  ." exceeded: more than 26 search parameters for"
1075  ." field ID ".$FieldId.".");
1076  }
1077 
1078  # add search string to URL
1079  $Params[self::$UrlParameterPrefix.$SetPrefix
1080  .$FieldId.$ParamSuffix] = $Value;
1081  $ParamSuffix = ($ParamSuffix == "") ? "A"
1082  : chr(ord($ParamSuffix) + 1);
1083  }
1084  }
1085 
1086  # for each keyword search string
1087  $ParamSuffix = "";
1088  foreach ($this->KeywordSearchStrings as $Value)
1089  {
1090  # check for too many keyword search strings
1091  if ($ParamSuffix == "Z")
1092  {
1093  throw new Exception("Maximum search parameter complexity"
1094  ." exceeded: more than 26 keyword search parameters.");
1095  }
1096 
1097  # add search string to URL
1098  $Params[self::$UrlParameterPrefix.$SetPrefix
1099  .self::URL_KEYWORD_INDICATOR.$ParamSuffix] = $Value;
1100  $ParamSuffix = ($ParamSuffix == "") ? "A"
1101  : chr(ord($ParamSuffix) + 1);
1102  }
1103 
1104  # add logic if not default
1105  if ($this->Logic != self::DEFAULT_LOGIC)
1106  {
1107  $Params[self::$UrlParameterPrefix.$SetPrefix
1108  .self::URL_LOGIC_INDICATOR] = $this->Logic;
1109  }
1110 
1111  # for each search parameter subgroup
1112  $SetLetter = "A";
1113  foreach ($this->Subgroups as $Subgroup)
1114  {
1115  # check for too many subgroups
1116  if ($SetLetter == "Z")
1117  {
1118  throw new Exception("Maximum search parameter complexity"
1119  ." exceeded: more than 24 search parameter subgroups.");
1120  }
1121 
1122  # retrieve URL string for subgroup and add it to URL
1123  $Params = array_merge($Params, $Subgroup->GetAsUrlParameters(
1124  $SetPrefix.$SetLetter));
1125 
1126  # move to next set letter
1127  $SetLetter = ($SetLetter == chr(ord(self::URL_KEYWORD_INDICATOR) - 1))
1128  ? chr(ord(self::URL_KEYWORD_INDICATOR) + 1)
1129  : chr(ord($SetLetter) + 1);
1130  }
1131 
1132  # return constructed URL parameter string to caller
1133  return $Params;
1134  }
1135 
1142  private function SetFromUrlParameters($UrlParameters)
1143  {
1144  # if string was passed in
1145  if (is_string($UrlParameters))
1146  {
1147  # split string into parameter array
1148  $Params = explode("&", $UrlParameters);
1149 
1150  # pare down parameter array to search parameter elements
1151  # and strip off search parameter prefix
1152  $NewUrlParameters = array();
1153  foreach ($Params as $Param)
1154  {
1155  if (strpos($Param, self::$UrlParameterPrefix) === 0)
1156  {
1157  list($Index, $Value) = explode("=", $Param);
1158  $NewUrlParameters[$Index] = urldecode($Value);
1159  }
1160  }
1161  $UrlParameters = $NewUrlParameters;
1162  }
1163 
1164  # for each search parameter
1165  foreach ($UrlParameters as $ParamName => $SearchString)
1166  {
1167  # strip off standard search parameter prefix
1168  $ParamName = substr($ParamName, strlen(self::$UrlParameterPrefix));
1169 
1170  # split parameter into component parts
1171  $SplitResult = preg_match("/^([".self::URL_KEYWORDFREE_RANGE."]*)"
1172  ."([0-9".self::URL_KEYWORD_INDICATOR."]+)([A-Z]*)$/",
1173  $ParamName, $Matches);
1174 
1175  # if split was successful
1176  if ($SplitResult === 1)
1177  {
1178  # pull components from split pieces
1179  $SetPrefix = $Matches[1];
1180  $FieldId = $Matches[2];
1181  $ParamSuffix = $Matches[3];
1182 
1183  # if set prefix indicates parameter is part of our set
1184  if ($SetPrefix == "")
1185  {
1186  switch ($FieldId)
1187  {
1188  case self::URL_LOGIC_INDICATOR:
1189  # set logic
1190  $this->Logic($SearchString);
1191  break;
1192 
1193  case self::URL_KEYWORD_INDICATOR:
1194  # add string to keyword searches
1195  $this->KeywordSearchStrings[] = $SearchString;
1196  break;
1197 
1198  default:
1199  # add string to searches for appropriate field
1200  $this->SearchStrings[$FieldId][] = $SearchString;
1201  break;
1202  }
1203  }
1204  else
1205  {
1206  # add parameter to array for subgroup
1207  $SubgroupIndex = $SetPrefix[0];
1208  $SubgroupPrefix = (strlen($SetPrefix) > 1)
1209  ? substr($SetPrefix, 1) : "";
1210  $SubgroupParamIndex = self::$UrlParameterPrefix
1211  .$SubgroupPrefix.$FieldId.$ParamSuffix;
1212  $SubgroupParameters[$SubgroupIndex][$SubgroupParamIndex]
1213  = $SearchString;
1214  }
1215  }
1216  }
1217 
1218  # if subgroups were found
1219  if (isset($SubgroupParameters))
1220  {
1221  # for each identified subgroup
1222  foreach ($SubgroupParameters as $SubgroupIndex => $Parameters)
1223  {
1224  # create subgroup and set parameters
1225  $Subgroup = new SearchParameterSet();
1226  $Subgroup->SetFromUrlParameters($Parameters);
1227 
1228  # add subgroup to our set
1229  $this->Subgroups[] = $Subgroup;
1230  }
1231  }
1232  }
1233 
1239  private static function NormalizeField($Field)
1240  {
1241  if (($Field !== NULL) && isset(self::$CanonicalFieldFunction))
1242  {
1243  $Field = call_user_func(self::$CanonicalFieldFunction, $Field);
1244  }
1245  return $Field;
1246  }
1247 }
TextDescription($IncludeHtml=TRUE, $StartWithBreak=TRUE, $TruncateLongWordsTo=0, $Indent="")
Get text description of search parameter set.
Data($NewValue=NULL)
Get/set search parameter set data, in the form of an opaque string.
static TranslateLegacySearchValues($Field, $Values)
Translate legacy search values to modern equivalents if possible.
SetFromLegacyArray($SearchGroups)
Set search parameters from legacy array format.
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)
Get/set search parameter set, in the form of an URL parameter string.
UrlParameters($NewValue=NULL)
Get/set search parameter set, in the form of URL parameters.
Logic($NewValue=NULL)
Get/set logic for set.
GetFields()
Get fields used in search parameters (including subgroups).
static SetLegacyUrlTranslationFunction($Func)
Register function used to converrt values from the format used in a Legacy URL string to the modern v...
GetSearchStringsForField($Field, $IncludeSubgroups=TRUE)
Get search strings for specified field.
static SetPrintableFieldFunction($Func)
Register function used to retrieve a printable value for a field.
ItemTypes($ItemTypes=FALSE)
Get/set allowed item types.
SetFromLegacyUrl($ParameterString)
Set search parameters from legacy URL string.
__clone()
Class clone handler, implemented so that clones will be deep copies rather than PHP5&#39;s default shallo...
AddParameter($SearchStrings, $Field=NULL)
Add search parameter to set.
static ConvertLegacyUrl($ParameterString)
Convert legacy URL to the current URL format.
GetAsLegacyArray()
Retrieve search parameters in legacy array format.
GetSearchStrings($IncludeSubgroups=FALSE)
Get search strings in set.
__construct($Data=NULL)
Class constructor, used to create a new set or reload an existing set from previously-constructed dat...
RemoveParameter($SearchStrings, $Field=NULL)
Remove search parameter from set.
static IsLegacyUrl($ParameterString)
Determine if a URL uses legacy format.
GetKeywordSearchStrings()
Get keyword search strings in set.
ReplaceSearchString($Pattern, $Replacement)
Modify all search strings with the specified regular expression.
GetSubgroups()
Get parameter subgroups.
ParameterCount()
Get number of search parameters in set, including those in subgroups.