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())
291  {
292  $this->Subgroups[] = $Set;
293  }
294  }
295 
296 
297  /*@)*/
298  # ---- DATA RETRIEVAL -----------------------------------------------------
299  /*@(*/
300 
305  public function ParameterCount()
306  {
307  $Count = count($this->KeywordSearchStrings);
308  foreach ($this->SearchStrings as $Field => $Strings)
309  {
310  $Count += count($Strings);
311  }
312  foreach ($this->Subgroups as $Group)
313  {
314  $Count += $Group->ParameterCount();
315  }
316  return $Count;
317  }
318 
326  public function GetSearchStrings($IncludeSubgroups = FALSE)
327  {
328  $SearchStrings = $this->SearchStrings;
329  if ($IncludeSubgroups)
330  {
331  foreach ($this->Subgroups as $Group)
332  {
333  $SubStrings = $Group->GetSearchStrings(TRUE);
334  foreach ($SubStrings as $Field => $Strings)
335  {
336  if (isset($SearchStrings[$Field]))
337  {
338  $SearchStrings[$Field] = array_merge(
339  $SearchStrings[$Field], $Strings);
340  }
341  else
342  {
343  $SearchStrings[$Field] = $Strings;
344  }
345  }
346  }
347  }
348  return $SearchStrings;
349  }
350 
359  public function GetSearchStringsForField($Field, $IncludeSubgroups = TRUE)
360  {
361  # normalize field value
362  $Field = self::NormalizeField($Field);
363 
364  # start with our string values
365  $Strings = isset($this->SearchStrings[$Field])
366  ? $this->SearchStrings[$Field] : array();
367 
368  # if strings from subgroups should also be returned
369  if ($IncludeSubgroups)
370  {
371  # for each subgroup
372  foreach ($this->Subgroups as $Group)
373  {
374  # add any strings from that subgroup
375  $Strings = array_merge($Strings,
376  $Group->GetSearchStringsForField($Field));
377  }
378  }
379 
380  # return all strings found to caller
381  return $Strings;
382  }
383 
388  public function GetKeywordSearchStrings()
389  {
390  return $this->KeywordSearchStrings;
391  }
392 
397  public function GetSubgroups()
398  {
399  return $this->Subgroups;
400  }
401 
406  public function GetFields()
407  {
408  # retrieve our fields
409  $Fields = array_keys($this->SearchStrings);
410 
411  # for each subgroup
412  foreach ($this->Subgroups as $Group)
413  {
414  # add fields from subgroup to the list
415  $Fields = array_merge($Fields, $Group->GetFields());
416  }
417 
418  # filter out duplicates and sort to ensure consistency
419  $Fields = array_unique($Fields);
420  sort($Fields);
421 
422  # return list of field identifiers to caller
423  return $Fields;
424  }
425 
426 
427  /*@)*/
428  # ---- DATA TRANSLATION ---------------------------------------------------
429  /*@(*/
430 
441  public function Data($NewValue = NULL)
442  {
443  # if new data supplied
444  if ($NewValue !== NULL)
445  {
446  # unpack set data and load
447  $this->LoadFromData($NewValue);
448  }
449 
450  # serialize current data and return to caller
451  $Data = array();
452  if ($this->Logic !== "AND") { $Data["Logic"] = $this->Logic; }
453  if (count($this->SearchStrings))
454  { $Data["SearchStrings"] = $this->SearchStrings; }
455  if (count($this->KeywordSearchStrings))
456  {
457  $Data["KeywordSearchStrings"] = $this->KeywordSearchStrings;
458  }
459  if (count($this->Subgroups))
460  {
461  foreach ($this->Subgroups as $Subgroup)
462  {
463  $Data["Subgroups"][] = $Subgroup->Data();
464  }
465  }
466  return serialize($Data);
467  }
468 
475  public function UrlParameters($NewValue = NULL)
476  {
477  # if new value supplied
478  if ($NewValue !== NULL)
479  {
480  # set new parameters
481  $this->SetFromUrlParameters($NewValue);
482  }
483 
484  # get existing search parameters as URL parameters
485  $Params = $this->GetAsUrlParameters();
486 
487  # sort parameters by parameter name to normalize result
488  ksort($Params);
489 
490  # return parameters to caller
491  return $Params;
492  }
493 
500  public function UrlParameterString($NewValue = NULL)
501  {
502  # get/set parameters
503  $Params = $this->UrlParameters($NewValue);
504 
505  # combine values into string
506  $ParamString = "";
507  $Separator = "";
508  foreach ($Params as $Index => $Value)
509  {
510  $ParamString .= $Separator.$Index."=".urlencode($Value);
511  $Separator = "&";
512  }
513 
514  # return string to caller
515  return $ParamString;
516  }
517 
529  public function TextDescription($IncludeHtml = TRUE, $StartWithBreak = TRUE,
530  $TruncateLongWordsTo = 0, $Indent = "")
531  {
532  # define list of phrases used to represent logical operators
533  $OperatorPhrases = array(
534  "=" => "is",
535  "==" => "is",
536  ">" => "is greater than",
537  "<" => "is less than",
538  ">=" => "is at least",
539  "<=" => "is no more than",
540  "!" => "is not",
541  "!=" => "is not",
542  "^" => "begins with",
543  "$" => "ends with",
544  "@" => "was last modified on or after",
545  "@>" => "was last modified after",
546  "@>=" => "was last modified on or after",
547  "@<" => "was last modified before",
548  "@<=" => "was last modified on or before",
549  );
550  $AgoOperatorPhrases = array(
551  "@>" => "was last modified more than",
552  "@>=" => "was last modified at least or more than",
553  "@<" => "was last modified less than",
554  "@<=" => "was last modified at most or less than",
555  );
556 
557  # set characters used to indicate literal strings
558  $LiteralStart = $IncludeHtml ? "<i>" : "\"";
559  $LiteralEnd = $IncludeHtml ? "</i>" : "\"";
560  $LiteralBreak = $IncludeHtml ? "<br>\n" : "\n";
561  $Indent .= $IncludeHtml ? "&nbsp;&nbsp;&nbsp;&nbsp;" : " ";
562 
563  # for each keyword search string
564  $Descriptions = array();
565  foreach ($this->KeywordSearchStrings as $SearchString)
566  {
567  # escape search string if appropriate
568  if ($IncludeHtml)
569  {
570  $SearchString = defaulthtmlentities($SearchString);
571  }
572 
573  # add string to list of descriptions
574  $Descriptions[] = $LiteralStart.$SearchString.$LiteralEnd;
575  }
576 
577  # for each field with search strings
578  foreach ($this->SearchStrings as $FieldId => $SearchStrings)
579  {
580  # retrieve field name
581  $FieldName = call_user_func(self::$PrintableFieldFunction, $FieldId);
582 
583  # for each search string
584  foreach ($SearchStrings as $SearchString)
585  {
586  # extract operator from search string
587  $MatchResult = preg_match('/^([=><!^$@]+)(.+)/',
588  $SearchString, $Matches);
589 
590  # determine operator phrase
591  if (($MatchResult == 1) && isset($OperatorPhrases[$Matches[1]]))
592  {
593  if (isset($AgoOperatorPhrases[$Matches[1]])
594  && strstr($SearchString, "ago"))
595  {
596  $OpPhrase = $AgoOperatorPhrases[$Matches[1]];
597  }
598  else
599  {
600  $OpPhrase = $OperatorPhrases[$Matches[1]];
601  }
602  $SearchString = $Matches[2];
603  }
604  else
605  {
606  $OpPhrase = "contains";
607  }
608 
609  # escape field name and search string if appropriate
610  if ($IncludeHtml)
611  {
612  $FieldName = defaulthtmlentities($FieldName);
613  $SearchString = defaulthtmlentities($SearchString);
614  }
615 
616  # assemble field and operator and value into description
617  $Descriptions[] = $FieldName." ".$OpPhrase." "
618  .$LiteralStart.$SearchString.$LiteralEnd;
619  }
620  }
621 
622  # for each subgroup
623  foreach ($this->Subgroups as $Subgroup)
624  {
625  # retrieve description for subgroup
626  if ($Subgroup->ParameterCount() == 1)
627  {
628  $Descriptions[] = $Subgroup->TextDescription($IncludeHtml,
629  $StartWithBreak, $TruncateLongWordsTo, $Indent);
630  }
631  elseif ($Subgroup->ParameterCount() > 1)
632  {
633  $Descriptions[] = "(".$Subgroup->TextDescription($IncludeHtml,
634  $StartWithBreak, $TruncateLongWordsTo, $Indent).")";
635  }
636  }
637 
638  # join descriptions with appropriate conjunction
639  $Descrip = join($LiteralBreak.$Indent." ".strtolower($this->Logic)." ",
640  $Descriptions);
641 
642  # if caller requested that long words be truncated
643  if ($TruncateLongWordsTo > 4)
644  {
645  # break description into words
646  $Words = explode(" ", $Descrip);
647 
648  # for each word
649  $NewDescrip = "";
650  foreach ($Words as $Word)
651  {
652  # if word is longer than specified length
653  if (strlen(strip_tags($Word)) > $TruncateLongWordsTo)
654  {
655  # truncate word and add ellipsis
656  $Word = StdLib::NeatlyTruncateString($Word, $TruncateLongWordsTo - 3);
657  }
658 
659  # add word to new description
660  $NewDescrip .= " ".$Word;
661  }
662 
663  # set description to new description
664  $Descrip = $NewDescrip;
665  }
666 
667  if (isset(self::$TextDescriptionFilterFunction))
668  {
669  $Descrip = call_user_func_array(
670  self::$TextDescriptionFilterFunction,
671  [$Descrip]);
672  }
673 
674  # return description to caller
675  return $Descrip;
676  }
677 
678 
679  /*@)*/
680  # ---- UTILITY METHODS ----------------------------------------------------
681  /*@(*/
682 
688  public function ReplaceSearchString($Pattern, $Replacement)
689  {
690  # modify our fielded search strings
691  foreach ($this->SearchStrings as $Field => $Strings)
692  {
693  $this->SearchStrings[$Field] =
694  preg_replace($Pattern, $Replacement, $Strings);
695  }
696 
697  # modify our keyword search strings
698  if (count($this->KeywordSearchStrings))
699  {
700  $this->KeywordSearchStrings =
701  preg_replace($Pattern, $Replacement, $this->KeywordSearchStrings);
702  }
703 
704  # modify any subgroups
705  foreach ($this->Subgroups as $Group)
706  {
707  $Group->ReplaceSearchString($Pattern, $Replacement);
708  }
709  }
710 
711 
712  /*@)*/
713  # ---- BACKWARD COMPATIBILITY ---------------------------------------------
714  /*@(*/
715 
724  public function GetAsLegacyArray()
725  {
726  $Legacy = array();
727 
728  $Group = $this->ConvertToLegacyGroup();
729  if (count($Group))
730  {
731  $Legacy["MAIN"] = $Group;
732  }
733 
734  # for each subgroup
735  foreach ($this->Subgroups as $Subgroup)
736  {
737  # skip empty search groups
738  if (count($Subgroup->SearchStrings)==0)
739  {
740  continue;
741  }
742 
743  $SubLegacy = $Subgroup->ConvertToLegacyGroup();
744 
745  # give an index based on the FieldId of the first
746  # element in the SearchStrings
747  $FieldId = call_user_func(
748  self::$CanonicalFieldFunction,
749  current(array_keys($SubLegacy["SearchStrings"])) );
750 
751  # add groups from legacy array to our array
752  if (!isset($Legacy[$FieldId]))
753  {
754  $Legacy[$FieldId] = $SubLegacy;
755  }
756  else
757  {
758  $Num = count($Legacy[$FieldId]);
759  $Legacy[$FieldId."-".$Num] = $SubLegacy;
760  }
761 
762  if (count($Subgroup->Subgroups))
763  {
764  throw new Exception(
765  "Attempt to convert SearchParameterSet containing nested subgroups "
766  ."to legacy format");
767  }
768  }
769 
770  # return array to caller
771  return $Legacy;
772  }
773 
778  public function SetFromLegacyArray($SearchGroups)
779  {
780  # clear current settings
781  $this->KeywordSearchStrings = array();
782  $this->SearchStrings = array();
783  $this->Subgroups = array();
784 
785  # iterate over legacy search groups
786  foreach ($SearchGroups as $GroupId => $SearchGroup)
787  {
788  if ($GroupId == "MAIN")
789  {
790  # add terms from the main search group to ourself
791  $this->LoadFromLegacyGroup($SearchGroup);
792  }
793  else
794  {
795  # create subgroups for other groups
796  $Subgroup = new SearchParameterSet();
797  $Subgroup->LoadFromLegacyGroup($SearchGroup);
798 
799  # add any non-empty groups
800  if ($Subgroup->ParameterCount())
801  {
802  $this->AddSet($Subgroup);
803  }
804  }
805  }
806  }
807 
812  public function SetFromLegacyUrl($ParameterString)
813  {
814  # clear current settings
815  $this->KeywordSearchStrings = array();
816  $this->SearchStrings = array();
817  $this->Subgroups = array();
818 
819  # extact array of parameters from passed string
820  $GetVars = ParseQueryString($ParameterString);
821 
822  # iterate over the provided parameters
823  foreach ($GetVars as $Key => $Val)
824  {
825  # if this param gives search information
826  if (preg_match("/^([FGH])(K|[0-9]+)$/", $Key, $Matches))
827  {
828  # extract what kind of search it was which field
829  $Type = $Matches[1];
830  $FieldId = $Matches[2];
831 
832  # for 'contains' searches
833  if ($Type == "F")
834  {
835  # add this to our search strings
836  $this->AddParameter( $Val,
837  ($FieldId == "K" ? NULL : $FieldId) );
838  }
839  else
840  {
841  # otherwise, create a subgroup for this parameter
842  $Subgroup = new SearchParameterSet();
843 
844  # set logic based on the search type
845  $Subgroup->Logic($Type=="H" ? "AND" : "OR");
846 
847  # extract the values and add them to a subgroup
848  $Values = explode("-", $Val);
849  $Subgroup->AddParameter(
850  self::TranslateLegacySearchValues($FieldId, $Values),
851  $FieldId );
852 
853  # if subgroup was non-empty, slurp it up
854  if ($Subgroup->ParameterCount())
855  {
856  $this->AddSet($Subgroup);
857  }
858  }
859  }
860  }
861  }
862 
868  public static function IsLegacyUrl($ParameterString)
869  {
870  $QueryVars = ParseQueryString($ParameterString);
871 
872  return (array_key_exists("Q", $QueryVars) &&
873  $QueryVars["Q"] == "Y") ? TRUE : FALSE ;
874  }
875 
881  public static function ConvertLegacyUrl($ParameterString)
882  {
883  $SearchParams = new SearchParameterSet();
884  $SearchParams->SetFromLegacyUrl($ParameterString);
885 
886  return $SearchParams->UrlParameterString();
887  }
888 
895  public static function SetLegacyUrlTranslationFunction($Func)
896  {
897  if (is_callable($Func))
898  {
899  self::$LegacyUrlTranslationFunction = $Func;
900  }
901  else
902  {
903  throw new InvalidArgumentException("Invalid function supplied.");
904  }
905  }
906 
914  public static function SetTextDescriptionFilterFunction($Func)
915  {
916  if (is_callable($Func))
917  {
918  self::$TextDescriptionFilterFunction = $Func;
919  }
920  else
921  {
922  throw new InvalidArgumentException("Invalid function supplied.");
923  }
924  }
925 
932  public static function TranslateLegacySearchValues($Field, $Values)
933  {
934  if (($Field !== NULL) && isset(self::$LegacyUrlTranslationFunction))
935  {
936  $Values = call_user_func(self::$LegacyUrlTranslationFunction,
937  $Field, $Values);
938  }
939  return $Values;
940  }
941 
942  /*@)*/
943  # ---- PRIVATE INTERFACE -------------------------------------------------
944 
945  private $KeywordSearchStrings = array();
946  private $ItemTypes = NULL;
947  private $Logic = self::DEFAULT_LOGIC;
948  private $SearchStrings = array();
949  private $Subgroups = array();
950 
951  static private $CanonicalFieldFunction;
952  static private $PrintableFieldFunction;
953  static private $LegacyUrlTranslationFunction;
954  static private $TextDescriptionFilterFunction;
955  static private $UrlParameterPrefix = "F";
956 
957  const DEFAULT_LOGIC = "AND";
958  const URL_KEYWORDFREE_RANGE = "A-JL-Z";
960  const URL_LOGIC_INDICATOR = "00";
961 
967  private function LoadFromData($Serialized)
968  {
969  # unpack new data
970  $Data = unserialize($Serialized);
971  if (!is_array($Data))
972  {
973  throw new InvalidArgumentException("Incoming set data"
974  ." appears invalid.");
975  }
976 
977  # load logic
978  $this->Logic = isset($Data["Logic"]) ? $Data["Logic"] : "AND";
979 
980  # load search strings
981  $this->SearchStrings = isset($Data["SearchStrings"])
982  ? $Data["SearchStrings"] : array();
983  $this->KeywordSearchStrings = isset($Data["KeywordSearchStrings"])
984  ? $Data["KeywordSearchStrings"] : array();
985 
986  # load any subgroups
987  $this->Subgroups = array();
988  if (isset($Data["Subgroups"]))
989  {
990  foreach ($Data["Subgroups"] as $SubgroupData)
991  {
992  $this->Subgroups[] = new SearchParameterSet($SubgroupData);
993  }
994  }
995  }
996 
1001  private function ConvertToLegacyGroup()
1002  {
1003  $Group = array();
1004  # for each set of search strings
1005  foreach ($this->SearchStrings as $Field => $Strings)
1006  {
1007  # get text name of field
1008  $FieldName = call_user_func(self::$PrintableFieldFunction, $Field);
1009 
1010  # add set to group
1011  $Group["SearchStrings"][$FieldName] = $Strings;
1012  }
1013 
1014  # for each keyword search string
1015  foreach ($this->KeywordSearchStrings as $String)
1016  {
1017  # add string to keyword entry in group
1018  $Group["SearchStrings"]["XXXKeywordXXX"][] = $String;
1019  }
1020 
1021  # if we had any search terms
1022  if (count($Group))
1023  {
1024  # smash single-value arrays to a scalar
1025  foreach ($Group["SearchStrings"] as &$Tgt)
1026  {
1027  if (count($Tgt) == 1)
1028  {
1029  $Tgt = current($Tgt);
1030  }
1031  }
1032 
1033  # set logic for search group
1034  $Group["Logic"] = ($this->Logic == "OR")
1036  }
1037 
1038  return $Group;
1039  }
1040 
1045  private function LoadFromLegacyGroup($Group)
1046  {
1047  # set logic appropriately
1048  $this->Logic(
1049  ($Group["Logic"] == SearchEngine::LOGIC_OR) ?
1050  "OR" : "AND" );
1051 
1052  # if this group had no search strings, we're done
1053  if (!isset($Group["SearchStrings"]))
1054  {
1055  return;
1056  }
1057 
1058  # otherwise, load the search strings
1059  foreach ($Group["SearchStrings"] as $Field => $Params)
1060  {
1061  # skip empty groups
1062  if (count($Params)==0)
1063  {
1064  continue;
1065  }
1066 
1067  $this->AddParameter( $Params,
1068  ($Field == "XXXKeywordXXX" ? NULL : $Field) );
1069 
1070  }
1071  }
1072 
1078  private function GetAsUrlParameters($SetPrefix = "")
1079  {
1080  # for each search string group in set
1081  $Params = array();
1082  foreach ($this->SearchStrings as $FieldId => $Values)
1083  {
1084  # get numeric version of field ID if not already numeric
1085  if (!is_numeric($FieldId))
1086  {
1087  $FieldId = call_user_func(self::$CanonicalFieldFunction, $FieldId);
1088  }
1089 
1090  # for each search string in group
1091  $ParamSuffix = "";
1092  foreach ($Values as $Value)
1093  {
1094  # check for too many search strings for this field
1095  if ($ParamSuffix == "Z")
1096  {
1097  throw new Exception("Maximum search parameter complexity"
1098  ." exceeded: more than 26 search parameters for"
1099  ." field ID ".$FieldId.".");
1100  }
1101 
1102  # add search string to URL
1103  $Params[self::$UrlParameterPrefix.$SetPrefix
1104  .$FieldId.$ParamSuffix] = $Value;
1105  $ParamSuffix = ($ParamSuffix == "") ? "A"
1106  : chr(ord($ParamSuffix) + 1);
1107  }
1108  }
1109 
1110  # for each keyword search string
1111  $ParamSuffix = "";
1112  foreach ($this->KeywordSearchStrings as $Value)
1113  {
1114  # check for too many keyword search strings
1115  if ($ParamSuffix == "Z")
1116  {
1117  throw new Exception("Maximum search parameter complexity"
1118  ." exceeded: more than 26 keyword search parameters.");
1119  }
1120 
1121  # add search string to URL
1122  $Params[self::$UrlParameterPrefix.$SetPrefix
1123  .self::URL_KEYWORD_INDICATOR.$ParamSuffix] = $Value;
1124  $ParamSuffix = ($ParamSuffix == "") ? "A"
1125  : chr(ord($ParamSuffix) + 1);
1126  }
1127 
1128  # add logic if not default
1129  if ($this->Logic != self::DEFAULT_LOGIC)
1130  {
1131  $Params[self::$UrlParameterPrefix.$SetPrefix
1132  .self::URL_LOGIC_INDICATOR] = $this->Logic;
1133  }
1134 
1135  # for each search parameter subgroup
1136  $SetLetter = "A";
1137  foreach ($this->Subgroups as $Subgroup)
1138  {
1139  # check for too many subgroups
1140  if ($SetLetter == "Z")
1141  {
1142  throw new Exception("Maximum search parameter complexity"
1143  ." exceeded: more than 24 search parameter subgroups.");
1144  }
1145 
1146  # retrieve URL string for subgroup and add it to URL
1147  $Params = array_merge($Params, $Subgroup->GetAsUrlParameters(
1148  $SetPrefix.$SetLetter));
1149 
1150  # move to next set letter
1151  $SetLetter = ($SetLetter == chr(ord(self::URL_KEYWORD_INDICATOR) - 1))
1152  ? chr(ord(self::URL_KEYWORD_INDICATOR) + 1)
1153  : chr(ord($SetLetter) + 1);
1154  }
1155 
1156  # return constructed URL parameter string to caller
1157  return $Params;
1158  }
1159 
1166  private function SetFromUrlParameters($UrlParameters)
1167  {
1168  # if string was passed in
1169  if (is_string($UrlParameters))
1170  {
1171  # split string into parameter array
1172  $Params = explode("&", $UrlParameters);
1173 
1174  # pare down parameter array to search parameter elements
1175  # and strip off search parameter prefix
1176  $NewUrlParameters = array();
1177  foreach ($Params as $Param)
1178  {
1179  if (strpos($Param, self::$UrlParameterPrefix) === 0)
1180  {
1181  list($Index, $Value) = explode("=", $Param);
1182  $NewUrlParameters[$Index] = urldecode($Value);
1183  }
1184  }
1185  $UrlParameters = $NewUrlParameters;
1186  }
1187 
1188  # for each search parameter
1189  foreach ($UrlParameters as $ParamName => $SearchString)
1190  {
1191  # strip off standard search parameter prefix
1192  $ParamName = substr($ParamName, strlen(self::$UrlParameterPrefix));
1193 
1194  # split parameter into component parts
1195  $SplitResult = preg_match("/^([".self::URL_KEYWORDFREE_RANGE."]*)"
1196  ."([0-9".self::URL_KEYWORD_INDICATOR."]+)([A-Z]*)$/",
1197  $ParamName, $Matches);
1198 
1199  # if split was successful
1200  if ($SplitResult === 1)
1201  {
1202  # pull components from split pieces
1203  $SetPrefix = $Matches[1];
1204  $FieldId = $Matches[2];
1205  $ParamSuffix = $Matches[3];
1206 
1207  # if set prefix indicates parameter is part of our set
1208  if ($SetPrefix == "")
1209  {
1210  switch ($FieldId)
1211  {
1212  case self::URL_LOGIC_INDICATOR:
1213  # set logic
1214  $this->Logic($SearchString);
1215  break;
1216 
1217  case self::URL_KEYWORD_INDICATOR:
1218  # add string to keyword searches
1219  $this->KeywordSearchStrings[] = $SearchString;
1220  break;
1221 
1222  default:
1223  # add string to searches for appropriate field
1224  $this->SearchStrings[$FieldId][] = $SearchString;
1225  break;
1226  }
1227  }
1228  else
1229  {
1230  # add parameter to array for subgroup
1231  $SubgroupIndex = $SetPrefix[0];
1232  $SubgroupPrefix = (strlen($SetPrefix) > 1)
1233  ? substr($SetPrefix, 1) : "";
1234  $SubgroupParamIndex = self::$UrlParameterPrefix
1235  .$SubgroupPrefix.$FieldId.$ParamSuffix;
1236  $SubgroupParameters[$SubgroupIndex][$SubgroupParamIndex]
1237  = $SearchString;
1238  }
1239  }
1240  }
1241 
1242  # if subgroups were found
1243  if (isset($SubgroupParameters))
1244  {
1245  # for each identified subgroup
1246  foreach ($SubgroupParameters as $SubgroupIndex => $Parameters)
1247  {
1248  # create subgroup and set parameters
1249  $Subgroup = new SearchParameterSet();
1250  $Subgroup->SetFromUrlParameters($Parameters);
1251 
1252  # add subgroup to our set
1253  $this->Subgroups[] = $Subgroup;
1254  }
1255  }
1256  }
1257 
1263  private static function NormalizeField($Field)
1264  {
1265  if (($Field !== NULL) && isset(self::$CanonicalFieldFunction))
1266  {
1267  $Field = call_user_func(self::$CanonicalFieldFunction, $Field);
1268  }
1269  return $Field;
1270  }
1271 }
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.
static NeatlyTruncateString($String, $MaxLength, $BreakAnywhere=FALSE)
Attempt to truncate a string as neatly as possible with respect to word breaks, punctuation, and HTML tags.
Definition: StdLib.php:237
__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.
static SetTextDescriptionFilterFunction($Func)
Register function used to filter text descriptions of searches prior to display.