3 # FILE: SavedSearch.php
6 # - the "$SearchGroups" values used herein contain a multi-dimentional
7 # array in the form of:
8 # $Criteria["MAIN"]["SearchStrings"][<field names>] = <value>
9 # for fields with a single value, and:
10 # $Criteria[<field ID>]["SearchStrings"][<field name>][] = <value>
11 # for fields with multiple values
14 # Part of the Collection Workflow Integration System (CWIS)
15 # Copyright 2011-2013 Edward Almasy and Internet Scout Research Group
16 # http://scout.wisc.edu/cwis/
21 # ---- PUBLIC INTERFACE --------------------------------------------------
23 # search frequency mnemonics
34 function SavedSearch($SearchId, $SearchName = NULL, $UserId = NULL,
35 $Frequency = NULL, $SearchGroups = NULL)
37 # get our own database handle
40 # if search ID was provided
41 if ($SearchId !== NULL)
44 $this->SearchId = intval($SearchId);
46 # initialize our local copies of data
47 $this->DB->Query(
"SELECT * FROM SavedSearches"
48 .
" WHERE SearchId = '".$this->SearchId.
"'");
49 $this->Record = $this->DB->FetchRow();
51 # update search details where provided
52 if ($SearchName) { $this->
SearchName($SearchName); }
53 if ($UserId) { $this->
UserId($UserId); }
54 if ($Frequency) { $this->
Frequency($Frequency); }
58 # add new saved search to database
59 $this->DB->Query(
"INSERT INTO SavedSearches"
60 .
" (SearchName, UserId, Frequency) VALUES ("
61 .
"'".addslashes($SearchName).
"', "
63 .intval($Frequency).
")");
65 # retrieve and save ID of new search locally
66 $this->SearchId = $this->DB->LastInsertId();
68 # save frequency and user ID locally
69 $this->Record[
"SearchName"] = $SearchName;
70 $this->Record[
"UserId"] = $UserId;
71 $this->Record[
"Frequency"] = $Frequency;
73 #Add the correct search parameters if they are provided
74 #and save an initial set of matches
84 # add search group to contain our added conditions
85 $NextGroupIndex = count($SearchGroups);
91 # signal event to allow modification of search parameters
92 $SignalResult = $GLOBALS[
"AF"]->SignalEvent(
93 "EVENT_FIELDED_SEARCH", array(
94 "SearchGroups" => $SearchGroups,
96 "SavedSearch" => $this));
97 $SearchGroups = $SignalResult[
"SearchGroups"];
101 $SearchResults = $SearchEngine->GroupedSearch($SearchGroups, 0, PHP_INT_MAX);
103 $NewItemIds = array_keys($SearchResults);
105 #Only allow resources the user can view
106 $NewItemIds = $RFactory->FilterNonViewableResources($NewItemIds, $EndUser);
108 # if search results were found
109 if (count($NewItemIds))
117 # get/set search parameters
122 # if new search parameters were supplied
123 if ($NewSearchGroups)
125 # remove existing entries for this search from the database
126 $this->DB->Query(
"DELETE FROM SavedSearchTextParameters WHERE SearchId = ".$this->SearchId);
127 $this->DB->Query(
"DELETE FROM SavedSearchIdParameters WHERE SearchId = ".$this->SearchId);
129 # for each search group
130 foreach ($NewSearchGroups as $GroupIndex => $Group)
132 # if group holds single parameters
133 if ($GroupIndex ==
"MAIN")
135 # for each field within group
136 foreach ($Group[
"SearchStrings"] as $FieldName => $Value)
138 # convert value array to single value (if necessary)
139 if (is_array($Value))
141 $ConvertedValue =
"";
142 foreach ($Value as $SingleValue)
144 $ConvertedValue .= $SingleValue.
" ";
146 $Value = trim($ConvertedValue);
149 # add new text search parameter entry to database
150 if ($FieldName ==
"XXXKeywordXXX")
156 $Field = $Schema->GetFieldByName($FieldName);
157 $FieldId = $Field->Id();
159 $this->DB->Query(
"INSERT INTO SavedSearchTextParameters"
160 .
" (SearchId, FieldId, SearchText) VALUES"
161 .
" (".$this->SearchId.
", ".$FieldId.
", '".addslashes($Value).
"')");
166 # convert value(s) as appropriate for field type
167 $FieldId = ($GroupIndex[0] ==
"X")
168 ? substr($GroupIndex, 1)
170 $Field = $Schema->GetField($FieldId);
171 $FieldName = $Field->Name();
172 $Values = SavedSearch::TranslateValues($Field, $Group[
"SearchStrings"][$FieldName],
"SearchGroup to Database");
174 # for each converted value
175 foreach ($Values as $Value)
177 # add new ID search parameter entry to database
178 $this->DB->Query(
"INSERT INTO SavedSearchIdParameters"
179 .
" (SearchId, FieldId, SearchValueId) VALUES"
180 .
" (".$this->SearchId.
", ".$FieldId.
", ".$Value.
")");
185 # save search parameters locally
190 # if search groups not already read in
193 # for each text search parameter
194 $SearchGroups = array();
195 $this->DB->Query(
"SELECT * FROM SavedSearchTextParameters"
196 .
" WHERE SearchId = ".$this->SearchId);
197 while ($Record = $this->DB->FetchRow())
199 # add parameter to search criteria
200 if ($Record[
"FieldId"] == -101)
202 $SearchGroups[
"MAIN"][
"SearchStrings"][
"XXXKeywordXXX"] =
203 $Record[
"SearchText"];
207 $Field = $Schema->GetField($Record[
"FieldId"]);
208 $SearchGroups[
"MAIN"][
"SearchStrings"][$Field->Name()] =
209 $Record[
"SearchText"];
213 # for each value ID search parameter
214 $this->DB->Query(
"SELECT * FROM SavedSearchIdParameters"
215 .
" WHERE SearchId = ".$this->SearchId);
216 while ($Record = $this->DB->FetchRow())
218 # translate value based on field type
219 $FieldId = $Record[
"FieldId"];
220 if (!isset($Fields[$FieldId]))
222 $Fields[$FieldId] = $Schema->GetField($FieldId);
224 $Values = SavedSearch::TranslateValues($Fields[$FieldId],
225 $Record[
"SearchValueId"],
"Database to SearchGroup");
227 # add parameter to search criteria
228 foreach ($Values as $Value)
230 $SearchGroups[$FieldId][
"SearchStrings"]
231 [$Fields[$FieldId]->Name()][] = $Value;
235 # set appropriate logic in search parameters
236 foreach ($SearchGroups as $GroupIndex => $Group)
238 $SearchGroups[$GroupIndex][
"Logic"] =
243 # save search parameters locally
248 # return search parameters to caller
249 return $this->SearchGroups;
258 {
return $this->UpdateValue(
"SearchName", $NewValue); }
264 function Id() {
return $this->SearchId; }
272 {
return $this->UpdateValue(
"UserId", $NewValue); }
280 {
return $this->UpdateValue(
"Frequency", $NewValue); }
282 # set date search was last run to current date/time
285 $this->DB->Query(
"UPDATE SavedSearches SET DateLastRun = NOW() WHERE SearchId = ".$this->SearchId);
288 # get/set date search was last run
290 {
return $this->UpdateValue(
"DateLastRun", $NewValue); }
298 $NewValue = implode(
",", $ArrayofMatchingIds);
299 $this->UpdateValue(
"LastMatchingIds", $NewValue);
308 return explode(
",", $this->DB->Query(
"SELECT LastMatchingIds FROM SavedSearches WHERE SearchId=".intval($this->SearchId),
"LastMatchingIds"));
317 return self::TranslateSearchGroupsToUrlParameters($this->
SearchGroups());
359 # assume that no parameters will be found
362 # for each group in parameters
364 foreach ($SearchGroups as $GroupIndex => $Group)
366 # if group holds single parameters
367 if ($GroupIndex ==
"MAIN")
369 # for each field within group
370 foreach ($Group[
"SearchStrings"] as $FieldName => $Value)
372 # add segment to URL for this field
373 if ($FieldName ==
"XXXKeywordXXX")
379 $Field = $Schema->GetFieldByName($FieldName);
380 $FieldId = $Field->Id();
382 if (is_array($Value))
384 $UrlPortion .=
"&F".$FieldId.
"=";
386 foreach ($Value as $SingleValue)
388 $ValueString .= $SingleValue.
" ";
390 $UrlPortion .= urlencode(trim($ValueString));
394 $UrlPortion .=
"&F".$FieldId.
"=".urlencode($Value);
400 # convert value based on field type
401 $FieldId = ($GroupIndex[0] ==
"X")
402 ? substr($GroupIndex, 1)
404 $Field = $Schema->GetField($FieldId);
405 $FieldName = $Field->Name();
406 $Values = SavedSearch::TranslateValues($Field,
407 $Group[
"SearchStrings"][$FieldName],
408 "SearchGroup to Database");
412 foreach ($Values as $Value)
417 $UrlPortion .=
"&G".$FieldId.
"=".$Value;
421 $UrlPortion .=
"-".$Value;
427 # trim off any leading "&"
428 if (strlen($UrlPortion)) { $UrlPortion = substr($UrlPortion, 1); }
430 # return URL portion to caller
441 return self::TranslateSearchGroupsToUrlParameters($this->
SearchGroups());
452 # assume that no parameters will be found
453 $UrlPortion = array();
455 # for each group in parameters
457 foreach ($SearchGroups as $GroupIndex => $Group)
459 # if group holds single parameters
460 if ($GroupIndex ==
"MAIN")
462 # for each field within group
463 foreach ($Group[
"SearchStrings"] as $FieldName => $Value)
465 # add segment to URL for this field
466 if ($FieldName ==
"XXXKeywordXXX")
472 $Field = $Schema->GetFieldByName($FieldName);
473 $FieldId = $Field->Id();
475 if (is_array($Value))
478 foreach ($Value as $SingleValue)
480 $ValueString .= $SingleValue.
" ";
483 $UrlPortion[
"F".$FieldId] = urlencode(trim($ValueString));
487 $UrlPortion[
"F".$FieldId] = urlencode($Value);
493 # convert value based on field type
494 $FieldId = ($GroupIndex[0] ==
"X")
495 ? substr($GroupIndex, 1)
497 $Field = $Schema->GetField($FieldId);
498 $FieldName = $Field->Name();
499 $Values = SavedSearch::TranslateValues($Field,
500 $Group[
"SearchStrings"][$FieldName],
501 "SearchGroup to Database");
507 foreach ($Values as $Value)
512 $UrlPortion[$LeadChar.$FieldId] = $Value;
516 $UrlPortion[$LeadChar.$FieldId] .=
"-".$Value;
522 # return URL portion to caller
526 # set search groups from URL (GET method) parameters
527 # (returns search group array)
530 # if URL segment was passed in instead of GET var array
531 if (is_string($GetVars))
533 $GetVars = ParseQueryString($GetVars);
536 # start with empty list of parameters
537 $SearchGroups = array();
540 $AllFields = $Schema->GetFields(NULL, NULL, TRUE);
542 foreach ($AllFields as $Field)
544 $FieldId = $Field->Id();
545 $FieldName = $Field->Name();
547 # if URL included literal value for this field
548 if (isset($GetVars[
"F".$FieldId]))
550 # retrieve value and add to search parameters
551 $SearchGroups[
"MAIN"][
"SearchStrings"][$FieldName] =
552 $GetVars[
"F".$FieldId];
555 # if URL included group value for this field
556 if (isset($GetVars[
"G".$FieldId]))
558 # retrieve and parse out values
559 $Values = explode(
"-", $GetVars[
"G".$FieldId]);
562 $Values = SavedSearch::TranslateValues($Field, $Values,
563 "Database to SearchGroup");
565 # add values to searchgroups
566 $SearchGroups[$FieldId][
"SearchStrings"][$FieldName] = $Values;
569 # if URL included group value for this field
570 if (isset($GetVars[
"H".$FieldId]))
572 # retrieve and parse out values
573 $Values = explode(
"-", $GetVars[
"H".$FieldId]);
576 $Values = SavedSearch::TranslateValues($Field, $Values,
577 "Database to SearchGroup");
579 # add values to searchgroups
580 $SearchGroups[
"X".$FieldId][
"SearchStrings"][$FieldName] = $Values;
584 # if keyword pseudo-field was included in URL
585 if (isset($GetVars[
"FK"]))
587 # retrieve value and add to search parameters
588 $SearchGroups[
"MAIN"][
"SearchStrings"][
"XXXKeywordXXX"] = $GetVars[
"FK"];
592 foreach ($SearchGroups as $GroupIndex => $Group)
594 $SearchGroups[$GroupIndex][
"Logic"] = ($GroupIndex ==
"MAIN")
596 : (($GroupIndex[0] ==
"X")
600 # return parameters to caller
601 return $SearchGroups;
615 $IncludeHtml = TRUE, $StartWithBreak = TRUE, $TruncateLongWordsTo = 0)
617 return self::TranslateSearchGroupsToTextDescription($this->
SearchGroups(),
618 $IncludeHtml, $StartWithBreak, $TruncateLongWordsTo);
633 $IncludeHtml = TRUE, $StartWithBreak = TRUE, $TruncateLongWordsTo = 0)
637 # start with empty description
640 # set characters used to indicate literal strings
641 $LiteralStart = $IncludeHtml ?
"<i>" :
"\"";
642 $LiteralEnd = $IncludeHtml ?
"</i>" :
"\"";
643 $LiteralBreak = $IncludeHtml ?
"<br>\n" :
"\n";
645 # if this is a simple keyword search
646 if (isset($SearchGroups[
"MAIN"][
"SearchStrings"][
"XXXKeywordXXX"])
647 && (count($SearchGroups) == 1)
648 && (count($SearchGroups[
"MAIN"][
"SearchStrings"]) == 1))
650 # just use the search string
651 $Descrip .= $LiteralStart;
652 $Descrip .= defaulthtmlentities($SearchGroups[
"MAIN"][
"SearchStrings"][
"XXXKeywordXXX"]);
653 $Descrip .= $LiteralEnd . $LiteralBreak;
657 # start description on a new line (if requested)
660 $Descrip .= $LiteralBreak;
663 # define list of phrases used to represent logical operators
664 $WordsForOperators = array(
666 ">" =>
"is greater than",
667 "<" =>
"is less than",
668 ">=" =>
"is at least",
669 "<=" =>
"is no more than",
673 # for each search group
674 foreach ($SearchGroups as $GroupIndex => $Group)
677 if ($GroupIndex ==
"MAIN")
679 # for each field in group
680 foreach ($Group[
"SearchStrings"] as $FieldName => $Value)
682 # determine wording based on operator
683 preg_match(
"/^[=><!]+/", $Value, $Matches);
684 if (count($Matches) && isset($WordsForOperators[$Matches[0]]))
686 $Value = preg_replace(
"/^[=><!]+/",
"", $Value);
687 $Wording = $WordsForOperators[$Matches[0]];
691 $Wording =
"contains";
694 # if field is psuedo-field
695 if ($FieldName ==
"XXXKeywordXXX")
697 # add criteria for psuedo-field
698 $Descrip .=
"Keyword ".$Wording.
" "
699 .$LiteralStart.htmlspecialchars($Value)
700 .$LiteralEnd.$LiteralBreak;
705 $Field = $Schema->GetFieldByName($FieldName);
708 # add criteria for field
709 $Descrip .= $Field->GetDisplayName().
" ".$Wording.
" "
710 .$LiteralStart.htmlspecialchars($Value)
711 .$LiteralEnd.$LiteralBreak;
718 # for each field in group
721 foreach ($Group[
"SearchStrings"] as $FieldName => $Values)
724 $Values = SavedSearch::TranslateValues(
725 $FieldName, $Values,
"SearchGroup to Display");
729 foreach ($Values as $Value)
731 # determine wording based on operator
732 preg_match(
"/^[=><!]+/", $Value, $Matches);
733 $Operator = $Matches[0];
734 $Wording = $WordsForOperators[$Operator];
737 $Value = preg_replace(
"/^[=><!]+/",
"", $Value);
739 # add text to description
742 $Descrip .= $FieldName.
" ".$Wording.
" "
743 .$LiteralStart.htmlspecialchars($Value)
744 .$LiteralEnd.$LiteralBreak;
749 $Descrip .= ($IncludeHtml ?
" " :
" ")
750 .$LogicTerm.$Wording.
" ".$LiteralStart
751 .htmlspecialchars($Value).$LiteralEnd
760 # if caller requested that long words be truncated
761 if ($TruncateLongWordsTo > 4)
763 # break description into words
764 $Words = explode(
" ", $Descrip);
768 foreach ($Words as $Word)
770 # if word is longer than specified length
771 if (strlen(strip_tags($Word)) > $TruncateLongWordsTo)
773 # truncate word and add ellipsis
774 $Word = NeatlyTruncateString($Word, $TruncateLongWordsTo - 3);
777 # add word to new description
778 $NewDescrip .=
" ".$Word;
781 # set description to new description
782 $Descrip = $NewDescrip;
785 # return description to caller
795 return self::TranslateSearchGroupsToSearchFieldNames($this->
SearchGroups());
805 # start out assuming no fields are being searched
806 $FieldNames = array();
808 # for each search group defined
809 foreach ($SearchGroups as $GroupIndex => $Group)
811 # for each field in group
812 foreach ($Group[
"SearchStrings"] as $FieldName => $Values)
814 # add field name to list of fields being searched
815 $FieldNames[] = $FieldName;
819 # return list of fields being searched to caller
830 # define list with descriptions
832 self::SEARCHFREQ_NEVER =>
"Never",
833 self::SEARCHFREQ_HOURLY =>
"Hourly",
834 self::SEARCHFREQ_DAILY =>
"Daily",
835 self::SEARCHFREQ_WEEKLY =>
"Weekly",
836 self::SEARCHFREQ_BIWEEKLY =>
"Bi-Weekly",
837 self::SEARCHFREQ_MONTHLY =>
"Monthly",
838 self::SEARCHFREQ_QUARTERLY =>
"Quarterly",
839 self::SEARCHFREQ_YEARLY =>
"Yearly",
842 # for each argument passed in
843 $Args = func_get_args();
844 foreach ($Args as $Arg)
846 # remove value from list
847 $FreqDescr = array_diff_key($FreqDescr, array($Arg =>
""));
850 # return list to caller
859 $this->DB->Query(
"DELETE FROM SavedSearches"
860 .
" WHERE SearchId = ".intval($this->SearchId));
861 $this->DB->Query(
"DELETE FROM SavedSearchTextParameters"
862 .
" WHERE SearchId = ".intval($this->SearchId));
863 $this->DB->Query(
"DELETE FROM SavedSearchIdParameters"
864 .
" WHERE SearchId = ".intval($this->SearchId));
868 # ---- PRIVATE INTERFACE -------------------------------------------------
872 private $SearchGroups;
874 # utility function to convert between value representations
875 # (method accepts a value or array and always return an array)
876 # (this is needed because values are represented differently:
878 # in DB / in URL / in forms 0/1 123 456
879 # used in SearchGroups 0/1 jdoe cname
880 # displayed to user On/Off jdoe cname
881 # where "123" and "456" are option or controlled name IDs)
882 private static function TranslateValues($FieldOrFieldName, $Values, $TranslationType)
884 # start out assuming we won't find any values to translate
885 $ReturnValues = array();
887 # convert field name to field object if necessary
888 if (is_object($FieldOrFieldName))
890 $Field = $FieldOrFieldName;
896 $Field = $Schema->GetFieldByName($FieldOrFieldName);
899 # if incoming value is not an array
900 if (!is_array($Values))
902 # convert incoming value to an array
903 $Values = array($Values);
906 # for each incoming value
907 foreach ($Values as $Value)
909 switch ($TranslationType)
911 case "SearchGroup to Display":
912 # if field is Flag field
915 # translate value to true/false label and add leading operator
916 $ReturnValues[] = ($Value ==
"=1") ?
"=".$Field->FlagOnLabel() :
"=".$Field->FlagOffLabel();
918 elseif ($Field->Name() ==
"Cumulative Rating")
920 # translate numeric value to stars
921 $StarStrings = array(
928 preg_match(
"/[0-9]+$/", $Value, $Matches);
929 $Number = $Matches[0];
930 preg_match(
"/^[=><!]+/", $Value, $Matches);
931 $Operator = $Matches[0];
932 $ReturnValues[] = $Operator.$StarStrings[$Number];
937 $ReturnValues[] = $Value;
941 case "SearchGroup to Database":
942 # strip off leading operator on value
943 $Value = preg_replace(
"/^[=><!]+/",
"", $Value);
945 # look up index for value
948 # (for flag or number fields the value index is already what is used in SearchGroups)
951 $ReturnValues[] = $Value;
956 # (for user fields the value index is the user ID)
957 $User =
new CWUser(strval($Value));
960 $ReturnValues[] = $User->Id();
965 if (!isset($PossibleFieldValues))
967 $PossibleFieldValues = $Field->GetPossibleValues();
969 $NewValue = array_search($Value, $PossibleFieldValues);
970 if ($NewValue !== FALSE)
972 $ReturnValues[] = $NewValue;
977 $NewValue = $Field->GetIdForValue($Value);
978 if ($NewValue !== NULL)
980 $ReturnValues[] = $NewValue;
985 case "Database to SearchGroup":
986 # look up value for index
989 # (for flag fields the value index (0 or 1) is already what is used in Database)
992 $ReturnValues[] =
"=".$Value;
997 # (for flag fields the value index (0 or 1) is already what is used in Database)
1000 $ReturnValues[] =
">=".$Value;
1005 $User =
new CWUser(intval($Value));
1008 $ReturnValues[] =
"=".$User->Get(
"UserName");
1013 if (!isset($PossibleFieldValues))
1015 $PossibleFieldValues = $Field->GetPossibleValues();
1018 if (isset($PossibleFieldValues[$Value]))
1020 $ReturnValues[] =
"=".$PossibleFieldValues[$Value];
1025 $NewValue = $Field->GetValueForId($Value);
1026 if ($NewValue !== NULL)
1028 $ReturnValues[] =
"=".$NewValue;
1035 # return array of translated values to caller
1036 return $ReturnValues;
1041 # utility function for updating values in database
1042 private function UpdateValue($FieldName, $NewValue)
1044 return $this->DB->UpdateValue(
"SavedSearches", $FieldName, $NewValue,
1045 "SearchId = ".$this->SearchId, $this->Record);
1048 # legacy methods for backward compatibility
static TranslateUrlParametersToSearchGroups($GetVars)
SQL database abstraction object with smart query caching.
static TranslateSearchGroupsToUrlParameterArray($SearchGroups)
Translate a search group array to an URL parameter array.
const SEARCHFREQ_QUARTERLY
UserId($NewValue=DB_NOVALUE)
Get/set user ID.
Frequency($NewValue=DB_NOVALUE)
Get/set search frequency.
SavedSearch($SearchId, $SearchName=NULL, $UserId=NULL, $Frequency=NULL, $SearchGroups=NULL)
const SEARCHFREQ_BIWEEKLY
static TranslateSearchGroupsToTextDescription($SearchGroups, $IncludeHtml=TRUE, $StartWithBreak=TRUE, $TruncateLongWordsTo=0)
Translate search group array into multi-line string describing search criteria.
DateLastRun($NewValue=DB_NOVALUE)
static TranslateSearchGroupsToUrlParameters($SearchGroups)
Translate search group array into URL parameters (e.g.
GetSearchGroupsAsTextDescription($IncludeHtml=TRUE, $StartWithBreak=TRUE, $TruncateLongWordsTo=0)
Get multi-line string describing search criteria.
SearchGroups($NewSearchGroups=NULL)
GetSearchFieldNames()
Get list of fields to be searched.
Delete()
Delete saved search.
SearchName($NewValue=DB_NOVALUE)
GetSearchGroupsAsUrlParameterArray()
Get search groups as an URL parameter array.
GetSearchGroupsAsUrlParameters()
Get search groups as URL parameters (e.g.
LastMatches()
Return array of most recently matched ResourceIds for a search.
Factory for Resource objects.
CWIS-specific user class.
static GetSearchFrequencyList()
Get array of possible search frequency descriptions.
static TranslateSearchGroupsToSearchFieldNames($SearchGroups)
Extract list of fields to be searched from search group array.
SaveLastMatches($ArrayofMatchingIds)
Save array of last matches.