3 # FILE: SearchEngine.php 5 # Open Source Metadata Archive Search Engine (OSMASE) 6 # Copyright 2002-2016 Edward Almasy and Internet Scout Research Group 7 # http://scout.wisc.edu 16 # ---- PUBLIC INTERFACE -------------------------------------------------- 18 # possible types of logical operators 22 # flags used for indicating field types 28 # flags used for indicating word states 44 # create database object for our use 47 # save item access parameters 52 # set default debug state 66 public function AddField($FieldId, $FieldType, $ItemTypes,
67 $Weight, $UsedInKeywordSearch)
70 $this->FieldInfo[$FieldId][
"FieldType"] = $FieldType;
71 $this->FieldInfo[$FieldId][
"Weight"] = $Weight;
72 $this->FieldInfo[$FieldId][
"InKeywordSearch"] =
73 $UsedInKeywordSearch ? TRUE : FALSE;
74 $this->FieldInfo[$FieldId][
"ItemTypes"] = is_array($ItemTypes)
75 ? $ItemTypes : array($ItemTypes);
85 return $this->FieldInfo[$FieldId][
"FieldType"];
95 return $this->FieldInfo[$FieldId][
"Weight"];
105 return $this->FieldInfo[$FieldId][
"InKeywordSearch"];
118 # ---- search functions 139 $SearchParams, $StartingResult = 0, $NumberOfResults = PHP_INT_MAX,
140 $SortByField = NULL, $SortDescending = TRUE)
142 # if keyword search string was passed in 143 if (is_string($SearchParams))
145 # convert string to search parameter set 146 $SearchString = $SearchParams;
148 $SearchParams->AddParameter($SearchString);
151 # interpret and filter out magic debugging keyword (if any) 152 $KeywordStrings = $SearchParams->GetKeywordSearchStrings();
153 foreach ($KeywordStrings as $String)
155 $FilteredString = $this->ExtractDebugLevel($String);
156 if ($FilteredString != $String)
158 $SearchParams->RemoveParameter($String);
159 $SearchParams->AddParameter($FilteredString);
163 # save start time to use in calculating search time 164 $StartTime = microtime(TRUE);
166 # clear parsed search term list 167 $this->SearchTermList = array();
170 $Scores = $this->RawSearch($SearchParams);
172 # count, sort, and trim search result scores list 173 $Scores = $this->CleanScores($Scores, $StartingResult, $NumberOfResults,
174 $SortByField, $SortDescending);
177 $this->LastSearchTime = microtime(TRUE) - $StartTime;
179 # return search results to caller 180 $this->
DMsg(0,
"Ended up with ".$this->NumberOfResultsAvailable.
" results");
204 $SearchStrings, $StartingResult = 0, $NumberOfResults = 10,
205 $SortByField = NULL, $SortDescending = TRUE)
207 # pass off the request to grouped search (for now) if appropriate 210 return $this->GroupedSearch($SearchStrings, $StartingResult,
211 $NumberOfResults, $SortByField, $SortDescending);
214 # interpret and filter out magic debugging keyword (if any) 215 $SearchStrings = $this->SetDebugLevel($SearchStrings);
216 $this->
DMsg(0,
"In FieldedSearch() with " 217 .count($SearchStrings).
" search strings");
219 # save start time to use in calculating search time 220 $StartTime = microtime(TRUE);
223 $Scores = $this->SearchAcrossFields($SearchStrings);
224 $Scores = ($Scores === NULL) ? array() : $Scores;
226 # count, sort, and trim search result scores list 227 $Scores = $this->CleanScores($Scores, $StartingResult, $NumberOfResults,
228 $SortByField, $SortDescending);
231 $this->LastSearchTime = microtime(TRUE) - $StartTime;
233 # return list of items to caller 234 $this->
DMsg(0,
"Ended up with ".$this->NumberOfResultsAvailable.
" results");
244 # save filter function name 245 $this->FilterFuncs[] = $FunctionName;
256 return ($ItemType === NULL) ? $this->NumberOfResultsAvailable
257 : (isset($this->NumberOfResultsPerItemType[$ItemType])
258 ? $this->NumberOfResultsPerItemType[$ItemType] : 0);
267 return $this->SearchTermList;
288 $FieldIds = $SearchParams->GetFields();
289 foreach ($FieldIds as $FieldId)
291 if (array_key_exists($FieldId, $this->FieldInfo))
293 $Weight += $this->FieldInfo[$FieldId][
"Weight"];
296 if (count($SearchParams->GetKeywordSearchStrings()))
298 foreach ($this->FieldInfo as $FieldId => $Info)
300 if ($Info[
"InKeywordSearch"])
302 $Weight += $Info[
"Weight"];
310 # ---- search database update functions 319 # clear word count added flags for this item 320 unset($this->WordCountAdded);
322 # delete any existing info for this item 323 $this->DB->Query(
"DELETE FROM SearchWordCounts WHERE ItemId = ".$ItemId);
324 $this->DB->Query(
"DELETE FROM SearchItemTypes WHERE ItemId = ".$ItemId);
327 $this->DB->Query(
"INSERT INTO SearchItemTypes (ItemId, ItemType)" 328 .
" VALUES (".intval($ItemId).
", ".intval($ItemType).
")");
330 # for each metadata field 331 foreach ($this->FieldInfo as $FieldId => $Info)
333 # if valid search weight for field and field applies to this item 334 if (($Info[
"Weight"] > 0)
335 && in_array($ItemType, $Info[
"ItemTypes"]))
337 # retrieve text for field 343 # for each text string in array 344 foreach ($Text as $String)
346 # record search info for text 347 $this->RecordSearchInfoForText($ItemId, $FieldId,
348 $Info[
"Weight"], $String,
349 $Info[
"InKeywordSearch"]);
354 # record search info for text 355 $this->RecordSearchInfoForText($ItemId, $FieldId,
356 $Info[
"Weight"], $Text,
357 $Info[
"InKeywordSearch"]);
371 # retrieve IDs for specified number of items starting at specified ID 372 $this->DB->Query(
"SELECT ".$this->ItemIdFieldName.
", ".$this->ItemTypeFieldName
373 .
" FROM ".$this->ItemTableName
374 .
" WHERE ".$this->ItemIdFieldName.
" >= ".$StartingItemId
375 .
" ORDER BY ".$this->ItemIdFieldName.
" LIMIT ".$NumberOfItems);
376 $ItemIds = $this->DB->FetchColumn(
377 $this->ItemTypeFieldName, $this->ItemIdFieldName);
379 # for each retrieved item ID 380 foreach ($ItemIds as $ItemId => $ItemType)
382 # update search info for item 386 # return ID of last item updated to caller 396 # drop all entries pertaining to item from word count table 397 $this->DB->Query(
"DELETE FROM SearchWordCounts WHERE ItemId = ".$ItemId);
398 $this->DB->Query(
"DELETE FROM SearchItemTypes WHERE ItemId = ".$ItemId);
407 # drop all entries pertaining to field from word counts table 408 $this->DB->Query(
"DELETE FROM SearchWordCounts WHERE FieldId = \'".$FieldId.
"\'");
417 return $this->DB->Query(
"SELECT COUNT(*) AS TermCount" 418 .
" FROM SearchWords",
"TermCount");
427 return $this->DB->Query(
"SELECT COUNT(DISTINCT ItemId) AS ItemCount" 428 .
" FROM SearchWordCounts",
"ItemCount");
440 # asssume no synonyms will be added 444 $WordId = $this->GetWordId($Word, TRUE);
446 # for each synonym passed in 447 foreach ($Synonyms as $Synonym)
450 $SynonymId = $this->GetWordId($Synonym, TRUE);
452 # if synonym is not already in database 453 $this->DB->Query(
"SELECT * FROM SearchWordSynonyms" 454 .
" WHERE (WordIdA = ".$WordId
455 .
" AND WordIdB = ".$SynonymId.
")" 456 .
" OR (WordIdB = ".$WordId
457 .
" AND WordIdA = ".$SynonymId.
")");
458 if ($this->DB->NumRowsSelected() == 0)
460 # add synonym entry to database 461 $this->DB->Query(
"INSERT INTO SearchWordSynonyms" 462 .
" (WordIdA, WordIdB)" 463 .
" VALUES (".$WordId.
", ".$SynonymId.
")");
468 # report to caller number of new synonyms added 481 $WordId = $this->GetWordId($Word);
484 if ($WordId !== NULL)
486 # if no specific synonyms provided 487 if ($Synonyms === NULL)
489 # remove all synonyms for word 490 $this->DB->Query(
"DELETE FROM SearchWordSynonyms" 491 .
" WHERE WordIdA = '".$WordId.
"'" 492 .
" OR WordIdB = '".$WordId.
"'");
496 # for each specified synonym 497 foreach ($Synonyms as $Synonym)
499 # look up ID for synonym 500 $SynonymId = $this->GetWordId($Synonym);
502 # if synonym ID was found 503 if ($SynonymId !== NULL)
505 # delete synonym entry 506 $this->DB->Query(
"DELETE FROM SearchWordSynonyms" 507 .
" WHERE (WordIdA = '".$WordId.
"'" 508 .
" AND WordIdB = '".$SynonymId.
"')" 509 .
" OR (WordIdB = '".$WordId.
"'" 510 .
" AND WordIdA = '".$SynonymId.
"')");
522 $this->DB->Query(
"DELETE FROM SearchWordSynonyms");
532 # assume no synonyms will be found 535 # look up ID for word 536 $WordId = $this->GetWordId($Word);
538 # if word ID was found 539 if ($WordId !== NULL)
541 # look up IDs of all synonyms for this word 542 $this->DB->Query(
"SELECT WordIdA, WordIdB FROM SearchWordSynonyms" 543 .
" WHERE WordIdA = ".$WordId
544 .
" OR WordIdB = ".$WordId);
545 $SynonymIds = array();
546 while ($Record = $this->DB->FetchRow)
548 $SynonymIds[] = ($Record[
"WordIdA"] == $WordId)
549 ? $Record[
"WordIdB"] : $Record[
"WordIdA"];
552 # for each synonym ID 553 foreach ($SynonymIds as $SynonymId)
555 # look up synonym word and add to synonym list 556 $Synonyms[] = $this->GetWord($SynonymId);
560 # return synonyms to caller 570 # assume no synonyms will be found 571 $SynonymList = array();
573 # for each synonym ID pair 575 $OurDB->Query(
"SELECT WordIdA, WordIdB FROM SearchWordSynonyms");
576 while ($Record = $OurDB->FetchRow())
579 $Word = $this->GetWord($Record[
"WordIdA"]);
580 $Synonym = $this->GetWord($Record[
"WordIdB"]);
582 # if we do not already have an entry for the word 583 # or synonym is not listed for this word 584 if (!isset($SynonymList[$Word])
585 || !in_array($Synonym, $SynonymList[$Word]))
587 # add entry for synonym 588 $SynonymList[$Word][] = $Synonym;
591 # if we do not already have an entry for the synonym 592 # or word is not listed for this synonym 593 if (!isset($SynonymList[$Synonym])
594 || !in_array($Word, $SynonymList[$Synonym]))
597 $SynonymList[$Synonym][] = $Word;
602 # (this loop removes reciprocal duplicates) 603 foreach ($SynonymList as $Word => $Synonyms)
605 # for each synonym for that word 606 foreach ($Synonyms as $Synonym)
608 # if synonym has synonyms and word is one of them 609 if (isset($SynonymList[$Synonym])
610 && isset($SynonymList[$Word])
611 && in_array($Word, $SynonymList[$Synonym])
612 && in_array($Synonym, $SynonymList[$Word]))
614 # if word has less synonyms than synonym 615 if (count($SynonymList[$Word])
616 < count($SynonymList[$Synonym]))
618 # remove synonym from synonym list for word 619 $SynonymList[$Word] = array_diff(
620 $SynonymList[$Word], array($Synonym));
622 # if no synonyms left for word 623 if (!count($SynonymList[$Word]))
625 # remove empty synonym list for word 626 unset($SynonymList[$Word]);
631 # remove word from synonym list for synonym 632 $SynonymList[$Synonym] = array_diff(
633 $SynonymList[$Synonym], array($Word));
635 # if no synonyms left for word 636 if (!count($SynonymList[$Synonym]))
638 # remove empty synonym list for word 639 unset($SynonymList[$Synonym]);
646 # sort array alphabetically (just for convenience) 647 foreach ($SynonymList as $Word => $Synonyms)
649 asort($SynonymList[$Word]);
653 # return 2D array of synonyms to caller 664 # remove all existing synonyms 667 # for each synonym entry passed in 668 foreach ($SynonymList as $Word => $Synonyms)
670 # add synonyms for word 685 # asssume no synonyms will be added 688 # read in contents of file 689 $Lines = file($FileName, FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES);
691 # if file contained lines 694 # for each line of file 695 foreach ($Lines as $Line)
697 # if line is not a comment 698 if (!preg_match(
"/[\s]*#/", $Line))
700 # split line into words 701 $Words = preg_split(
"/[\s,]+/", $Line);
704 if (count($Words) > 1)
706 # separate out word and synonyms 707 $Word = array_shift($Words);
716 # return count of synonyms added to caller 721 # ---- PRIVATE INTERFACE ------------------------------------------------- 734 private $ExcludedTermCount;
737 private $InclusiveTermCount;
738 private $RequiredTermCount;
739 private $RequiredTermCounts;
740 private $SearchTermList;
741 private $WordCountAdded;
747 # ---- private methods (searching) 756 private function RawSearch($SearchParams)
758 # retrieve search strings 759 $SearchStrings = $SearchParams->GetSearchStrings();
760 $KeywordSearchStrings = $SearchParams->GetKeywordSearchStrings();
762 # add keyword searches (if any) to fielded searches 763 if (count($KeywordSearchStrings))
765 $SearchStrings[self::KEYWORD_FIELD_ID] = $KeywordSearchStrings;
768 # normalize search strings 769 $NormalizedSearchStrings = array();
770 foreach ($SearchStrings as $FieldId => $SearchStringArray)
772 if (!is_array($SearchStringArray))
774 $SearchStringArray = array($SearchStringArray);
776 foreach ($SearchStringArray as $String)
778 $String = trim($String);
781 $NormalizedSearchStrings[$FieldId][] = $String;
785 $SearchStrings = $NormalizedSearchStrings;
787 # if we have strings to search for 788 if (count($SearchStrings))
791 $Scores = $this->SearchAcrossFields(
792 $SearchStrings, $SearchParams->Logic());
796 foreach ($SearchParams->GetSubgroups() as $Subgroup)
798 # perform subgroup search 799 $NewScores = $this->RawSearch($Subgroup);
801 # added subgroup search scores to previous scores as appropriate 804 $Scores = $this->CombineScores(
805 $Scores, $NewScores, $SearchParams->Logic());
809 $Scores = $NewScores;
812 if (isset($NewScores))
814 $this->
DMsg(2,
"Have ".count($Scores)
815 .
" results after subgroup processing");
818 # pare down results to just allowed item types (if specified) 819 if ($SearchParams->ItemTypes())
821 $AllowedItemTypes = $SearchParams->ItemTypes();
822 foreach ($Scores as $ItemId => $Score)
824 if (!in_array($this->GetItemType($ItemId), $AllowedItemTypes))
826 unset($Scores[$ItemId]);
829 $this->
DMsg(3,
"Have ".count($Scores)
830 .
" results after paring to allowed item types");
833 # return search results to caller 834 return isset($Scores) ? $Scores : array();
844 private function CombineScores($ScoresA, $ScoresB, $Logic)
849 foreach ($ScoresB as $ItemId => $Score)
851 if (isset($Scores[$ItemId]))
853 $Scores[$ItemId] += $Score;
857 $Scores[$ItemId] = $Score;
864 foreach ($ScoresA as $ItemId => $Score)
866 if (isset($ScoresB[$ItemId]))
868 $Scores[$ItemId] = $Score + $ScoresB[$ItemId];
884 private function SearchAcrossFields($SearchStrings, $Logic)
886 # start by assuming no search will be done 890 $this->ExcludedTermCount = 0;
891 $this->InclusiveTermCount = 0;
892 $this->RequiredTermCount = 0;
893 $this->RequiredTermCounts = array();
896 $NeedComparisonSearch = FALSE;
897 foreach ($SearchStrings as $FieldId => $SearchStringArray)
899 # for each search string for this field 900 foreach ($SearchStringArray as $SearchString)
902 # if field is keyword or field is text and does not look 903 # like comparison match 904 $NotComparisonSearch = !preg_match(
905 self::COMPARISON_OPERATOR_PATTERN, $SearchString);
906 if (($FieldId == self::KEYWORD_FIELD_ID)
907 || (isset($this->FieldInfo[$FieldId])
908 && ($this->FieldInfo[$FieldId][
"FieldType"]
909 == self::FIELDTYPE_TEXT)
910 && $NotComparisonSearch))
912 $this->
DMsg(0,
"Searching text field \"" 913 .$FieldId.
"\" for string \"$SearchString\"");
915 # normalize text and split into words 917 $this->ParseSearchStringForWords($SearchString, $Logic);
919 # calculate scores for matching items 920 if (count($Words[$FieldId]))
922 $Scores = $this->SearchForWords(
923 $Words[$FieldId], $FieldId, $Scores);
924 $this->
DMsg(3,
"Have " 925 .count($Scores).
" results after word search");
929 $Phrases[$FieldId] = $this->ParseSearchStringForPhrases(
930 $SearchString, $Logic);
933 if (count($Phrases[$FieldId]))
935 $Scores = $this->SearchForPhrases(
936 $Phrases[$FieldId], $Scores, $FieldId, TRUE, FALSE);
937 $this->
DMsg(3,
"Have " 938 .count($Scores).
" results after phrase search");
943 # set flag to indicate possible comparison search candidate found 944 $NeedComparisonSearch = TRUE;
949 # perform comparison searches 950 if ($NeedComparisonSearch)
952 $Scores = $this->SearchForComparisonMatches(
953 $SearchStrings, $Logic, $Scores);
954 $this->
DMsg(3,
"Have ".count($Scores).
" results after comparison search");
957 # if no results found, no required terms, and exclusions specified 958 if ((count($Scores) == 0) &&
959 ($this->RequiredTermCount == 0) &&
960 ($this->ExcludedTermCount > 0) )
962 # determine which item types are implicated for keyword searches 963 $KeywordItemTypes = [];
964 foreach ($this->FieldInfo as $FieldId => $Info)
966 if ($Info[
"InKeywordSearch"])
968 $KeywordItemTypes = array_merge(
973 $KeywordItemTypes = array_unique($KeywordItemTypes);
975 # determine what item types were in use for the fields we 978 foreach ($SearchStrings as $FieldId => $Info)
980 $MyTypes = ($FieldId == self::KEYWORD_FIELD_ID) ?
982 $this->FieldInfo[$FieldId][
"ItemTypes"];
984 $FieldTypes = array_merge(
985 $FieldTypes, $MyTypes);
987 $FieldTypes = array_unique($FieldTypes);
989 # load all records for these field types 990 $Scores = $this->LoadScoresForAllRecords($FieldTypes);
993 # if search results found 996 # for each search text string 997 foreach ($SearchStrings as $FieldId => $SearchStringArray)
999 # for each search string for this field 1000 foreach ($SearchStringArray as $SearchString)
1003 if (($FieldId == self::KEYWORD_FIELD_ID)
1004 || (isset($this->FieldInfo[$FieldId])
1005 && ($this->FieldInfo[$FieldId][
"FieldType"]
1006 == self::FIELDTYPE_TEXT)))
1008 # if there are words in search text 1009 if (isset($Words[$FieldId]))
1011 # handle any excluded words 1012 $Scores = $this->FilterOnExcludedWords(
1013 $Words[$FieldId], $Scores, $FieldId);
1016 # handle any excluded phrases 1017 if (isset($Phrases[$FieldId]))
1019 $Scores = $this->SearchForPhrases(
1020 $Phrases[$FieldId], $Scores,
1021 $FieldId, FALSE, TRUE);
1025 $this->
DMsg(3,
"Have ".count($Scores)
1026 .
" results after processing exclusions");
1029 # strip off any results that don't contain required words 1030 $Scores = $this->FilterOnRequiredWords($Scores);
1033 # return search result scores to caller 1046 private function SearchForWords($Words, $FieldId, $Scores = NULL)
1050 # start with empty search result scores list if none passed in 1051 if ($Scores == NULL)
1057 foreach ($Words as $Word => $Flags)
1060 $this->
DMsg(2,
"Searching for word '${Word}' in field ".$FieldId);
1062 # if word is not excluded 1063 if (!($Flags & self::WORD_EXCLUDED))
1065 # look up record ID for word 1066 $this->
DMsg(2,
"Looking up word \"".$Word.
"\"");
1067 $WordId = $this->GetWordId($Word);
1070 if ($WordId !== NULL)
1072 # look up counts for word 1073 $DB->Query(
"SELECT ItemId,Count FROM SearchWordCounts " 1074 .
"WHERE WordId = ".$WordId
1075 .
" AND FieldId = ".$FieldId);
1076 $Counts =
$DB->FetchColumn(
"Count",
"ItemId");
1078 # if synonym support is enabled 1079 if ($this->SynonymsEnabled)
1081 # look for any synonyms 1082 $DB->Query(
"SELECT WordIdA, WordIdB" 1083 .
" FROM SearchWordSynonyms" 1084 .
" WHERE WordIdA = ".$WordId
1085 .
" OR WordIdB = ".$WordId);
1087 # if synonyms were found 1088 if (
$DB->NumRowsSelected())
1090 # retrieve synonym IDs 1091 $SynonymIds = array();
1092 while ($Record =
$DB->FetchRow())
1094 $SynonymIds[] = ($Record[
"WordIdA"] == $WordId)
1095 ? $Record[
"WordIdB"]
1096 : $Record[
"WordIdA"];
1100 foreach ($SynonymIds as $SynonymId)
1102 # retrieve counts for synonym 1103 $DB->Query(
"SELECT ItemId,Count" 1104 .
" FROM SearchWordCounts" 1105 .
" WHERE WordId = ".$SynonymId
1106 .
" AND FieldId = ".$FieldId);
1107 $SynonymCounts =
$DB->FetchColumn(
"Count",
"ItemId");
1110 foreach ($SynonymCounts as $ItemId => $Count)
1112 # adjust count because it's a synonym 1113 $AdjustedCount = ceil($Count / 2);
1115 # add count to existing counts 1116 if (isset($Counts[$ItemId]))
1118 $Counts[$ItemId] += $AdjustedCount;
1122 $Counts[$ItemId] = $AdjustedCount;
1130 # if stemming is enabled 1131 if ($this->StemmingEnabled)
1133 # retrieve word stem 1134 $Stem = PorterStemmer::Stem($Word);
1136 # if stem was different from word 1140 $this->
DMsg(2,
"Looking up stem \"".$Stem.
"\"");
1141 $StemId = $this->GetStemId($Stem);
1143 # if ID found for stem 1144 if ($StemId !== NULL)
1146 # retrieve counts for stem 1147 $DB->Query(
"SELECT ItemId,Count" 1148 .
" FROM SearchWordCounts" 1149 .
" WHERE WordId = ".$StemId
1150 .
" AND FieldId = ".$FieldId);
1151 $StemCounts =
$DB->FetchColumn(
"Count",
"ItemId");
1154 foreach ($StemCounts as $ItemId => $Count)
1156 # adjust count because it's a stem 1157 $AdjustedCount = ceil($Count / 2);
1159 # add count to existing counts 1160 if (isset($Counts[$ItemId]))
1162 $Counts[$ItemId] += $AdjustedCount;
1166 $Counts[$ItemId] = $AdjustedCount;
1173 # if counts were found 1177 foreach ($Counts as $ItemId => $Count)
1179 # if word flagged as required 1180 if ($Flags & self::WORD_REQUIRED)
1182 # increment required word count for record 1183 if (isset($this->RequiredTermCounts[$ItemId]))
1185 $this->RequiredTermCounts[$ItemId]++;
1189 $this->RequiredTermCounts[$ItemId] = 1;
1193 # add to item record score 1194 if (isset($Scores[$ItemId]))
1196 $Scores[$ItemId] += $Count;
1200 $Scores[$ItemId] = $Count;
1207 # return basic scores to caller 1218 private function ParseSearchStringForPhrases($SearchString, $Logic)
1220 # split into chunks delimited by double quote marks 1221 $Pieces = explode(
"\"", $SearchString); #
" 1223 # for each pair of chunks 1226 while ($Index < count($Pieces)) 1228 # grab phrase from chunk 1229 $Phrase = trim(addslashes($Pieces[$Index - 1])); 1230 $Flags = self::WORD_PRESENT; 1232 # grab first character of phrase 1233 $FirstChar = substr($Pieces[$Index - 2], -1); 1235 # set flags to reflect any option characters 1236 if ($FirstChar == "-
") 1238 $Flags |= self::WORD_EXCLUDED; 1239 if (!isset($Phrases[$Phrase])) 1241 $this->ExcludedTermCount++; 1246 if ((($Logic == "AND
") 1247 && ($FirstChar != "~
")) 1248 || ($FirstChar == "+
")) 1250 $Flags |= self::WORD_REQUIRED; 1251 if (!isset($Phrases[$Phrase])) 1253 $this->RequiredTermCount++; 1256 if (!isset($Phrases[$Phrase])) 1258 $this->InclusiveTermCount++; 1259 $this->SearchTermList[] = $Phrase; 1262 $Phrases[$Phrase] = $Flags; 1264 # move to next pair of chunks 1268 # return phrases to caller 1277 protected function SearchFieldForPhrases($FieldId, $Phrase) 1294 private function SearchForPhrases($Phrases, $Scores, $FieldId,
1295 $ProcessNonExcluded = TRUE, $ProcessExcluded = TRUE)
1297 # if phrases are found 1298 if (count($Phrases) > 0)
1300 # if this is a keyword search 1301 if ($FieldId == self::KEYWORD_FIELD_ID)
1304 foreach ($this->FieldInfo as $KFieldId => $Info)
1306 # if field is marked to be included in keyword searches 1307 if ($Info[
"InKeywordSearch"])
1309 # call ourself with that field 1310 $Scores = $this->SearchForPhrases(
1311 $Phrases, $Scores, $KFieldId,
1312 $ProcessNonExcluded, $ProcessExcluded);
1319 foreach ($Phrases as $Phrase => $Flags)
1321 $this->
DMsg(2,
"Searching for phrase '".$Phrase
1322 .
"' in field ".$FieldId);
1324 # if phrase flagged as excluded and we are doing excluded 1325 # phrases or phrase flagged as non-excluded and we 1326 # are doing non-excluded phrases 1327 if (($ProcessExcluded && ($Flags & self::WORD_EXCLUDED))
1328 || ($ProcessNonExcluded && !($Flags & self::WORD_EXCLUDED)))
1330 # initialize score list if necessary 1331 if ($Scores === NULL) { $Scores = array(); }
1333 # retrieve list of items that contain phrase 1337 # for each item that contains phrase 1338 foreach ($ItemIds as $ItemId)
1340 # if we are doing excluded phrases and phrase 1341 # is flagged as excluded 1342 if ($ProcessExcluded && ($Flags & self::WORD_EXCLUDED))
1344 # knock item off of list 1345 unset($Scores[$ItemId]);
1347 elseif ($ProcessNonExcluded)
1349 # calculate phrase value based on number of 1350 # words and field weight 1351 $PhraseScore = count(preg_split(
"/[\s]+/",
1352 $Phrase, -1, PREG_SPLIT_NO_EMPTY))
1353 * $this->FieldInfo[$FieldId][
"Weight"];
1354 $this->
DMsg(2,
"Phrase score is ".$PhraseScore);
1356 # bump up item record score 1357 if (isset($Scores[$ItemId]))
1359 $Scores[$ItemId] += $PhraseScore;
1363 $Scores[$ItemId] = $PhraseScore;
1366 # if phrase flagged as required 1367 if ($Flags & self::WORD_REQUIRED)
1369 # increment required word count for record 1370 if (isset($this->RequiredTermCounts[$ItemId]))
1372 $this->RequiredTermCounts[$ItemId]++;
1376 $this->RequiredTermCounts[$ItemId] = 1;
1386 # return updated scores to caller 1398 private function FilterOnExcludedWords($Words, $Scores, $FieldId)
1403 foreach ($Words as $Word => $Flags)
1405 # if word flagged as excluded 1406 if ($Flags & self::WORD_EXCLUDED)
1408 # look up record ID for word 1409 $WordId = $this->GetWordId($Word);
1412 if ($WordId !== NULL)
1414 # look up counts for word 1415 $DB->Query(
"SELECT ItemId FROM SearchWordCounts " 1416 .
"WHERE WordId=${WordId} AND FieldId=${FieldId}");
1419 while ($Record =
$DB->FetchRow())
1421 # if item record is in score list 1422 $ItemId = $Record[
"ItemId"];
1423 if (isset($Scores[$ItemId]))
1425 # remove item record from score list 1426 $this->
DMsg(3,
"Filtering out item ".$ItemId
1427 .
" because it contained word \"".$Word.
"\"");
1428 unset($Scores[$ItemId]);
1435 # returned filtered score list to caller 1444 private function FilterOnRequiredWords($Scores)
1446 # if there were required words 1447 if ($this->RequiredTermCount > 0)
1450 foreach ($Scores as $ItemId => $Score)
1452 # if item does not meet required word count 1453 if (!isset($this->RequiredTermCounts[$ItemId])
1454 || ($this->RequiredTermCounts[$ItemId]
1455 < $this->RequiredTermCount))
1458 $this->
DMsg(4,
"Filtering out item ".$ItemId
1459 .
" because it didn't have required word count of " 1460 .$this->RequiredTermCount
1461 .(isset($this->RequiredTermCounts[$ItemId])
1463 .$this->RequiredTermCounts[$ItemId]
1466 unset($Scores[$ItemId]);
1471 # return filtered list to caller 1487 private function CleanScores($Scores, $StartingResult, $NumberOfResults,
1488 $SortByField, $SortDescending)
1490 # perform any requested filtering 1491 $this->
DMsg(0,
"Have ".count($Scores).
" results before filter callbacks");
1494 # save total number of results available 1495 $this->NumberOfResultsAvailable = count($Scores);
1497 # sort search scores into item type bins 1498 $NewScores = array();
1499 foreach ($Scores as $Id => $Score)
1501 $ItemType = $this->GetItemType($Id);
1502 if ($ItemType !== NULL)
1504 $NewScores[$ItemType][$Id] = $Score;
1507 $Scores = $NewScores;
1509 # for each item type 1510 $NewSortByField = array();
1511 $NewSortDescending = array();
1512 foreach ($Scores as $ItemType => $TypeScores)
1514 # normalize sort field parameter 1515 $NewSortByField[$ItemType] = !is_array($SortByField) ? $SortByField
1516 : (isset($SortByField[$ItemType])
1517 ? $SortByField[$ItemType] : NULL);
1519 # normalize sort direction parameter 1520 $NewSortDescending[$ItemType] = !is_array($SortDescending) ? $SortDescending
1521 : (isset($SortDescending[$ItemType])
1522 ? $SortDescending[$ItemType] : TRUE);
1524 $SortByField = $NewSortByField;
1525 $SortDescending = $NewSortDescending;
1527 # for each item type 1528 foreach ($Scores as $ItemType => $TypeScores)
1530 # save number of results 1531 $this->NumberOfResultsPerItemType[$ItemType] = count($TypeScores);
1533 # if no sorting field specified 1534 if ($SortByField[$ItemType] === NULL)
1536 # sort result list by score 1537 if ($SortDescending[$ItemType])
1539 arsort($Scores[$ItemType], SORT_NUMERIC);
1543 asort($Scores[$ItemType], SORT_NUMERIC);
1548 # get list of item IDs in sorted order 1549 $SortedIds = $this->GetItemIdsSortedByField($ItemType,
1550 $SortByField[$ItemType], $SortDescending[$ItemType]);
1552 # if we have sorted item IDs 1553 if (count($SortedIds) && count($TypeScores))
1555 # strip sorted ID list down to those that appear in search results 1556 $SortedIds = array_intersect($SortedIds,
1557 array_keys($TypeScores));
1559 # rebuild score list in sorted order 1560 $NewScores = array();
1561 foreach ($SortedIds as $Id)
1563 $NewScores[$Id] = $TypeScores[$Id];
1565 $Scores[$ItemType] = $NewScores;
1569 # sort result list by score 1570 arsort($Scores[$ItemType], SORT_NUMERIC);
1574 # if subset of scores requested 1575 if (($StartingResult > 0) || ($NumberOfResults < PHP_INT_MAX))
1577 # trim scores back to requested subset 1578 $ScoresKeys = array_slice(array_keys($Scores[$ItemType]),
1579 $StartingResult, $NumberOfResults);
1580 $NewScores = array();
1581 foreach ($ScoresKeys as $Key)
1583 $NewScores[$Key] = $Scores[$ItemType][$Key];
1585 $Scores[$ItemType] = $NewScores;
1589 # returned cleaned search result scores list to caller 1600 # if filter functions have been set 1601 if (isset($this->FilterFuncs))
1604 foreach ($Scores as $ItemId => $Score)
1606 # for each filter function 1607 foreach ($this->FilterFuncs as $FuncName)
1609 # if filter function return TRUE for item 1610 if (call_user_func($FuncName, $ItemId))
1613 $this->
DMsg(2,
"Filter callback <i>".$FuncName
1614 .
"</i> rejected item ".$ItemId);
1615 unset($Scores[$ItemId]);
1617 # bail out of filter func loop 1624 # return filtered list to caller 1637 private function SearchForComparisonMatches($SearchStrings, $Logic, $Scores)
1641 foreach ($SearchStrings as $SearchFieldId => $SearchStringArray)
1643 # if field is not keyword 1644 if ($SearchFieldId != self::KEYWORD_FIELD_ID)
1646 # for each search string for this field 1647 foreach ($SearchStringArray as $SearchString)
1649 # look for comparison operators 1650 $FoundOperator = preg_match(
1651 self::COMPARISON_OPERATOR_PATTERN,
1652 $SearchString, $Matches);
1654 # if a comparison operator was found 1655 # or this is a field type that is always a comparison search 1656 if ($FoundOperator ||
1657 ($this->FieldInfo[$SearchFieldId][
"FieldType"]
1658 != self::FIELDTYPE_TEXT))
1660 # determine value to compare against 1661 $Value = trim(preg_replace(
1662 self::COMPARISON_OPERATOR_PATTERN,
'\2',
1665 # if no comparison operator was found 1666 if (!$FoundOperator)
1668 # assume comparison is equality 1669 $Operators[$Index] =
"=";
1673 # use operator from comparison match 1674 $Operators[$Index] = $Matches[1];
1677 # if operator was found 1678 if (isset($Operators[$Index]))
1681 $Values[$Index] = $Value;
1684 $FieldIds[$Index] = $SearchFieldId;
1685 $this->
DMsg(3,
"Added comparison (field = <i>" 1686 .$FieldIds[$Index].
"</i> op = <i>" 1687 .$Operators[$Index].
"</i> val = <i>" 1688 .$Values[$Index].
"</i>)");
1690 # move to next comparison array entry 1698 # if comparisons found 1699 if (isset($Operators))
1701 # perform comparisons on fields and gather results 1702 $Results = $this->SearchFieldsForComparisonMatches(
1703 $FieldIds, $Operators, $Values, $Logic);
1705 # if search logic is set to AND 1706 if ($Logic ==
"AND")
1708 # if results were found 1709 if (count($Results))
1711 # if there were no prior results and no terms for keyword search 1712 if ((count($Scores) == 0) && ($this->InclusiveTermCount == 0))
1714 # add all results to scores 1715 foreach ($Results as $ItemId)
1717 $Scores[$ItemId] = 1;
1722 # remove anything from scores that is not part of results 1723 foreach ($Scores as $ItemId => $Score)
1725 if (in_array($ItemId, $Results) == FALSE)
1727 unset($Scores[$ItemId]);
1740 # add result items to scores 1741 if ($Scores === NULL) { $Scores = array(); }
1742 foreach ($Results as $ItemId)
1744 if (isset($Scores[$ItemId]))
1746 $Scores[$ItemId] += 1;
1750 $Scores[$ItemId] = 1;
1756 # return results to caller 1767 private function SetDebugLevel($SearchStrings)
1769 # if search info is an array 1770 if (is_array($SearchStrings))
1772 # for each array element 1773 foreach ($SearchStrings as $FieldId => $SearchStringArray)
1775 # if element is an array 1776 if (is_array($SearchStringArray))
1778 # for each array element 1779 foreach ($SearchStringArray as $Index => $SearchString)
1781 # pull out search string if present 1782 $SearchStrings[$FieldId][$Index] =
1783 $this->ExtractDebugLevel($SearchString);
1788 # pull out search string if present 1789 $SearchStrings[$FieldId] =
1790 $this->ExtractDebugLevel($SearchStringArray);
1796 # pull out search string if present 1797 $SearchStrings = $this->ExtractDebugLevel($SearchStrings);
1800 # return new search info to caller 1801 return $SearchStrings;
1810 private function ExtractDebugLevel($SearchString)
1812 # if search string contains debug level indicator 1813 if (strstr($SearchString,
"DBUGLVL="))
1815 # remove indicator and set debug level 1816 $Level = preg_replace(
"/^\\s*DBUGLVL=([1-9]{1,2}).*/",
"\\1", $SearchString);
1820 $this->
DMsg(0,
"Setting debug level to ".$Level);
1821 $SearchString = preg_replace(
"/\s*DBUGLVL=${Level}\s*/",
"",
1826 # return (possibly) modified search string to caller 1827 return $SearchString;
1835 private function LoadScoresForAllRecords($ItemTypes)
1837 # if no item types were provided return an empty array 1838 if (count($ItemTypes)==0)
1843 # get all the ItemIds belonging to the given types 1844 $this->DB->Query(
"SELECT ".$this->ItemIdFieldName.
" AS ItemId" 1845 .
" FROM ".$this->ItemTableName
1846 .
" WHERE ".$this->ItemTypeFieldName.
" IN(".implode(
",", $ItemTypes).
")");
1848 # return array with all scores to caller 1849 return array_fill_keys($this->DB->FetchColumn(
"ItemId"), 1);
1852 # ---- private methods (search DB building) 1861 private function UpdateWordCount($Word, $ItemId, $FieldId, $Weight = 1)
1863 # retrieve ID for word 1864 $WordIds[] = $this->GetWordId($Word, TRUE);
1866 # if stemming is enabled and word looks appropriate for stemming 1867 if ($this->StemmingEnabled && !is_numeric($Word))
1869 # retrieve stem of word 1870 $Stem = PorterStemmer::Stem($Word, TRUE);
1872 # if stem is different 1875 # retrieve ID for stem of word 1876 $WordIds[] = $this->GetStemId($Stem, TRUE);
1880 # for word and stem of word 1881 foreach ($WordIds as $WordId)
1883 # if word count already added to database 1884 if (isset($this->WordCountAdded[$WordId][$FieldId]))
1887 $this->DB->Query(
"UPDATE SearchWordCounts SET Count=Count+".$Weight
1888 .
" WHERE WordId=".$WordId
1889 .
" AND ItemId=".$ItemId
1890 .
" AND FieldId=".$FieldId);
1894 # add word count to DB 1895 $this->DB->Query(
"INSERT INTO SearchWordCounts" 1896 .
" (WordId, ItemId, FieldId, Count) VALUES" 1897 .
" (".$WordId.
", ".$ItemId.
", ".$FieldId.
", ".$Weight.
")");
1899 # remember that we added count for this word 1900 $this->WordCountAdded[$WordId][$FieldId] = TRUE;
1903 # decrease weight for stem 1904 $Weight = ceil($Weight / 2);
1916 throw Exception(
"GetFieldContent() not implemented.");
1928 private function RecordSearchInfoForText(
1929 $ItemId, $FieldId, $Weight, $Text, $IncludeInKeyword)
1932 $Words = $this->ParseSearchStringForWords($Text,
"OR", TRUE);
1934 # if there was text left after parsing 1935 if (count($Words) > 0)
1938 foreach ($Words as $Word => $Flags)
1940 # update count for word 1941 $this->UpdateWordCount($Word, $ItemId, $FieldId);
1943 # if text should be included in keyword searches 1944 if ($IncludeInKeyword)
1946 # update keyword field count for word 1947 $this->UpdateWordCount(
1948 $Word, $ItemId, self::KEYWORD_FIELD_ID, $Weight);
1954 # ---- common private methods (used in both searching and DB build) 1966 private function ParseSearchStringForWords(
1967 $SearchString, $Logic, $IgnorePhrases = FALSE)
1969 # strip off any surrounding whitespace 1970 $Text = trim($SearchString);
1972 # define phrase and group search patterns separately, so that we can 1973 # later replace them easily if necessary 1974 $PhraseSearchPattern =
"/\"[^\"]*\"/";
1975 $GroupSearchPattern =
"/\\([^)]*\\)/";
1977 # set up search string normalization replacement strings (NOTE: these 1978 # are performed in sequence, so the order IS SIGNIFICANT) 1979 $ReplacementPatterns = array(
1980 #
get rid of possessive plurals
1981 "/'s[^a-z0-9\\-+~]+/i" =>
" ",
1982 #
get rid of single quotes / apostrophes
1984 #
get rid of phrases
1985 $PhraseSearchPattern =>
" ",
1987 $GroupSearchPattern =>
" ",
1988 # convert everything but alphanumerics and minus/plus/tilde to a space
1989 "/[^a-z0-9\\-+~]+/i" =>
"\\1 ",
1990 # truncate any runs of minus/plus/tilde to just the first
char 1991 "/([~+-])[~+-]+/" =>
"\\1",
1992 # convert two alphanumerics segments separated by a minus into
1993 # both separate words and a single combined word
1994 "/([~+-]?)([a-z0-9]+)-([a-z0-9]+)/i" =>
"\\1\\2 \\1\\3 \\1\\2\\3",
1995 # convert minus/plus/tilde preceded by anything but whitespace to a space
1996 "/([^\\s])[~+-]+/i" =>
"\\1 ",
1997 # convert minus/plus/tilde followed by whitespace to a space
1998 "/[~+-]+\\s/i" =>
" ",
1999 # convert multiple spaces to one space
2003 # if we are supposed to ignore phrasing (series of words in quotes) 2004 # and grouping (series of words surrounded by parens) 2007 # switch phrase removal to double quote removal 2008 # and switch group removal to paren removal 2009 foreach ($ReplacementPatterns as $Pattern => $Replacement)
2011 if ($Pattern == $PhraseSearchPattern)
2015 elseif ($Pattern == $GroupSearchPattern)
2017 $Pattern =
"/[\(\)]+/";
2019 $NewReplacementPatterns[$Pattern] = $Replacement;
2021 $ReplacementPatterns = $NewReplacementPatterns;
2024 # remove punctuation from text and normalize whitespace 2025 $Text = preg_replace(array_keys($ReplacementPatterns),
2026 $ReplacementPatterns, $Text);
2027 $this->
DMsg(2,
"Normalized search string is '".$Text.
"'");
2029 # convert text to lower case 2030 $Text = strtolower($Text);
2032 # strip off any extraneous whitespace 2033 $Text = trim($Text);
2035 # start with an empty array 2038 # if we have no words left after parsing 2039 if (strlen($Text) != 0)
2042 foreach (explode(
" ", $Text) as $Word)
2044 # grab first character of word 2045 $FirstChar = substr($Word, 0, 1);
2047 # strip off option characters and set flags appropriately 2048 $Flags = self::WORD_PRESENT;
2049 if ($FirstChar ==
"-")
2051 $Word = substr($Word, 1);
2052 $Flags |= self::WORD_EXCLUDED;
2053 if (!isset($Words[$Word]))
2055 $this->ExcludedTermCount++;
2060 if ($FirstChar ==
"~")
2062 $Word = substr($Word, 1);
2064 elseif (($Logic ==
"AND")
2065 || ($FirstChar ==
"+"))
2067 if ($FirstChar ==
"+")
2069 $Word = substr($Word, 1);
2071 $Flags |= self::WORD_REQUIRED;
2072 if (!isset($Words[$Word]))
2074 $this->RequiredTermCount++;
2077 if (!isset($Words[$Word]))
2079 $this->InclusiveTermCount++;
2080 $this->SearchTermList[] = $Word;
2084 # store flags to indicate word found 2085 $Words[$Word] = $Flags;
2086 $this->
DMsg(3,
"Word identified (".$Word.
")");
2090 # return normalized words to caller 2101 private function GetWordId($Word, $AddIfNotFound = FALSE)
2103 static $WordIdCache;
2105 # if word was in ID cache 2106 if (isset($WordIdCache[$Word]))
2109 $WordId = $WordIdCache[$Word];
2113 # look up ID in database 2114 $WordId = $this->DB->Query(
"SELECT WordId" 2115 .
" FROM SearchWords" 2116 .
" WHERE WordText='".addslashes($Word).
"'",
2119 # if ID was not found and caller requested it be added 2120 if (($WordId === NULL) && $AddIfNotFound)
2122 # add word to database 2123 $this->DB->Query(
"INSERT INTO SearchWords (WordText)" 2124 .
" VALUES ('".addslashes(strtolower($Word)).
"')");
2126 # get ID for newly added word 2127 $WordId = $this->DB->LastInsertId();
2131 $WordIdCache[$Word] = $WordId;
2134 # return ID to caller 2145 private function GetStemId($Stem, $AddIfNotFound = FALSE)
2147 static $StemIdCache;
2149 # if stem was in ID cache 2150 if (isset($StemIdCache[$Stem]))
2153 $StemId = $StemIdCache[$Stem];
2157 # look up ID in database 2158 $StemId = $this->DB->Query(
"SELECT WordId" 2159 .
" FROM SearchStems" 2160 .
" WHERE WordText='".addslashes($Stem).
"'",
2163 # if ID was not found and caller requested it be added 2164 if (($StemId === NULL) && $AddIfNotFound)
2166 # add stem to database 2167 $this->DB->Query(
"INSERT INTO SearchStems (WordText)" 2168 .
" VALUES ('".addslashes(strtolower($Stem)).
"')");
2170 # get ID for newly added stem 2171 $StemId = $this->DB->LastInsertId();
2174 # adjust from DB ID value to stem ID value 2175 $StemId += self::STEM_ID_OFFSET;
2178 $StemIdCache[$Stem] = $StemId;
2181 # return ID to caller 2190 private function GetWord($WordId)
2194 # if word was in cache 2195 if (isset($WordCache[$WordId]))
2197 # use word from cache 2198 $Word = $WordCache[$WordId];
2202 # adjust search location and word ID if word is stem 2203 $TableName =
"SearchWords";
2204 if ($WordId >= self::STEM_ID_OFFSET)
2206 $TableName =
"SearchStems";
2207 $WordId -= self::STEM_ID_OFFSET;
2210 # look up word in database 2211 $Word = $this->DB->Query(
"SELECT WordText" 2212 .
" FROM ".$TableName
2213 .
" WHERE WordId='".$WordId.
"'",
2216 # save word to cache 2217 $WordCache[$WordId] = $Word;
2220 # return word to caller 2229 private function GetItemType($ItemId)
2231 static $ItemTypeCache;
2232 if (!isset($ItemTypeCache))
2234 $this->DB->Query(
"SELECT * FROM SearchItemTypes");
2235 $ItemTypeCache = $this->DB->FetchColumn(
"ItemType",
"ItemId");
2237 return isset($ItemTypeCache[$ItemId])
2238 ? (int)$ItemTypeCache[$ItemId] : NULL;
2246 protected function DMsg($Level, $Msg)
2250 print
"SE: ".$Msg.
"<br>\n";
2254 # ---- BACKWARD COMPATIBILITY -------------------------------------------- 2256 # possible types of logical operators 2260 # pattern to detect search strings that are explicit comparisons
SearchTermCount()
Get total number of search terms indexed by search engine.
SetAllSynonyms($SynonymList)
Set all synonyms.
DropItem($ItemId)
Drop all data pertaining to item from search database.
AddField($FieldId, $FieldType, $ItemTypes, $Weight, $UsedInKeywordSearch)
Add field to include in searching.
RemoveSynonyms($Word, $Synonyms=NULL)
Remove synonym(s).
LoadSynonymsFromFile($FileName)
Load synonyms from a file.
Set of parameters used to perform a search.
SQL database abstraction object with smart query caching.
SearchFieldForPhrases($FieldId, $Phrase)
Search for phrase in specified field.
GetAllSynonyms()
Get all synonyms.
FilterOnSuppliedFunctions($Scores)
Filter search scores through any supplied functions.
UpdateForItem($ItemId, $ItemType)
Update search database for the specified item.
AddSynonyms($Word, $Synonyms)
Add synonyms.
const FIELDTYPE_DATERANGE
SearchTerms()
Get normalized list of search terms.
NumberOfResults($ItemType=NULL)
Get number of results found by most recent search.
FieldWeight($FieldId)
Get search weight for specified field.
FieldType($FieldId)
Get type of specified field (text/numeric/date/daterange).
ItemCount()
Get total number of items indexed by search engine.
FieldedSearch($SearchStrings, $StartingResult=0, $NumberOfResults=10, $SortByField=NULL, $SortDescending=TRUE)
Perform search across multiple fields, with different values or comparisons specified for each field...
__construct($ItemTableName, $ItemIdFieldName, $ItemTypeFieldName)
Object constructor.
Search($SearchParams, $StartingResult=0, $NumberOfResults=PHP_INT_MAX, $SortByField=NULL, $SortDescending=TRUE)
Perform search with specified parameters.
RemoveAllSynonyms()
Remove all synonyms.
DMsg($Level, $Msg)
Print debug message if level set high enough.
DropField($FieldId)
Drop all data pertaining to field from search database.
GetFieldContent($ItemId, $FieldId)
Retrieve content for specified field for specified item.
Core metadata archive search engine class.
$NumberOfResultsAvailable
const COMPARISON_OPERATOR_PATTERN
DebugLevel($NewValue)
Set debug output level.
UpdateForItems($StartingItemId, $NumberOfItems)
Update search database for the specified range of items.
FieldedSearchWeightScale($SearchParams)
Get total of weights for all fields involved in search, useful for assessing scale of scores in searc...
FieldInKeywordSearch($FieldId)
Get whether specified field is included in keyword searches.
AddResultFilterFunction($FunctionName)
Add function that will be called to filter search results.
SearchTime()
Get time that last search took, in seconds.
GetSynonyms($Word)
Get synonyms for word.