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);
162 $this->
DMsg(0,
"Description: ".$SearchParams->TextDescription());
164 # save start time to use in calculating search time 165 $StartTime = microtime(TRUE);
167 # clear parsed search term list 168 $this->SearchTermList = array();
171 $Scores = $this->RawSearch($SearchParams);
173 # count, sort, and trim search result scores list 174 $Scores = $this->CleanScores($Scores, $StartingResult, $NumberOfResults,
175 $SortByField, $SortDescending);
178 $this->LastSearchTime = microtime(TRUE) - $StartTime;
180 # return search results to caller 181 $this->
DMsg(0,
"Ended up with ".$this->NumberOfResultsAvailable.
" results");
205 $SearchStrings, $StartingResult = 0, $NumberOfResults = 10,
206 $SortByField = NULL, $SortDescending = TRUE)
208 # pass off the request to grouped search (for now) if appropriate 211 return $this->GroupedSearch($SearchStrings, $StartingResult,
212 $NumberOfResults, $SortByField, $SortDescending);
215 # interpret and filter out magic debugging keyword (if any) 216 $SearchStrings = $this->SetDebugLevel($SearchStrings);
217 $this->
DMsg(0,
"In FieldedSearch() with " 218 .count($SearchStrings).
" search strings");
220 # save start time to use in calculating search time 221 $StartTime = microtime(TRUE);
224 $Scores = $this->SearchAcrossFields($SearchStrings);
225 $Scores = ($Scores === NULL) ? array() : $Scores;
227 # count, sort, and trim search result scores list 228 $Scores = $this->CleanScores($Scores, $StartingResult, $NumberOfResults,
229 $SortByField, $SortDescending);
232 $this->LastSearchTime = microtime(TRUE) - $StartTime;
234 # return list of items to caller 235 $this->
DMsg(0,
"Ended up with ".$this->NumberOfResultsAvailable.
" results");
245 # save filter function name 246 $this->FilterFuncs[] = $FunctionName;
257 return ($ItemType === NULL) ? $this->NumberOfResultsAvailable
258 : (isset($this->NumberOfResultsPerItemType[$ItemType])
259 ? $this->NumberOfResultsPerItemType[$ItemType] : 0);
268 return $this->SearchTermList;
289 $FieldIds = $SearchParams->GetFields();
290 foreach ($FieldIds as $FieldId)
292 if (array_key_exists($FieldId, $this->FieldInfo))
294 $Weight += $this->FieldInfo[$FieldId][
"Weight"];
297 if (count($SearchParams->GetKeywordSearchStrings()))
299 foreach ($this->FieldInfo as $FieldId => $Info)
301 if ($Info[
"InKeywordSearch"])
303 $Weight += $Info[
"Weight"];
311 # ---- search database update functions 320 # clear word count added flags for this item 321 unset($this->WordCountAdded);
323 # delete any existing info for this item 324 $this->DB->Query(
"DELETE FROM SearchWordCounts WHERE ItemId = ".$ItemId);
325 $this->DB->Query(
"DELETE FROM SearchItemTypes WHERE ItemId = ".$ItemId);
328 $this->DB->Query(
"INSERT INTO SearchItemTypes (ItemId, ItemType)" 329 .
" VALUES (".intval($ItemId).
", ".intval($ItemType).
")");
331 # for each metadata field 332 foreach ($this->FieldInfo as $FieldId => $Info)
334 # if valid search weight for field and field applies to this item 335 if (($Info[
"Weight"] > 0)
336 && in_array($ItemType, $Info[
"ItemTypes"]))
338 # retrieve text for field 344 # for each text string in array 345 foreach ($Text as $String)
347 # record search info for text 348 $this->RecordSearchInfoForText($ItemId, $FieldId,
349 $Info[
"Weight"], $String,
350 $Info[
"InKeywordSearch"]);
355 # record search info for text 356 $this->RecordSearchInfoForText($ItemId, $FieldId,
357 $Info[
"Weight"], $Text,
358 $Info[
"InKeywordSearch"]);
372 # retrieve IDs for specified number of items starting at specified ID 373 $this->DB->Query(
"SELECT ".$this->ItemIdFieldName.
", ".$this->ItemTypeFieldName
374 .
" FROM ".$this->ItemTableName
375 .
" WHERE ".$this->ItemIdFieldName.
" >= ".$StartingItemId
376 .
" ORDER BY ".$this->ItemIdFieldName.
" LIMIT ".$NumberOfItems);
377 $ItemIds = $this->DB->FetchColumn(
378 $this->ItemTypeFieldName, $this->ItemIdFieldName);
380 # for each retrieved item ID 381 foreach ($ItemIds as $ItemId => $ItemType)
383 # update search info for item 387 # return ID of last item updated to caller 397 # drop all entries pertaining to item from word count table 398 $this->DB->Query(
"DELETE FROM SearchWordCounts WHERE ItemId = ".$ItemId);
399 $this->DB->Query(
"DELETE FROM SearchItemTypes WHERE ItemId = ".$ItemId);
408 # drop all entries pertaining to field from word counts table 409 $this->DB->Query(
"DELETE FROM SearchWordCounts WHERE FieldId = \'".$FieldId.
"\'");
418 return $this->DB->Query(
"SELECT COUNT(*) AS TermCount" 419 .
" FROM SearchWords",
"TermCount");
428 return $this->DB->Query(
"SELECT COUNT(DISTINCT ItemId) AS ItemCount" 429 .
" FROM SearchWordCounts",
"ItemCount");
441 # asssume no synonyms will be added 445 $WordId = $this->GetWordId($Word, TRUE);
447 # for each synonym passed in 448 foreach ($Synonyms as $Synonym)
451 $SynonymId = $this->GetWordId($Synonym, TRUE);
453 # if synonym is not already in database 454 $this->DB->Query(
"SELECT * FROM SearchWordSynonyms" 455 .
" WHERE (WordIdA = ".$WordId
456 .
" AND WordIdB = ".$SynonymId.
")" 457 .
" OR (WordIdB = ".$WordId
458 .
" AND WordIdA = ".$SynonymId.
")");
459 if ($this->DB->NumRowsSelected() == 0)
461 # add synonym entry to database 462 $this->DB->Query(
"INSERT INTO SearchWordSynonyms" 463 .
" (WordIdA, WordIdB)" 464 .
" VALUES (".$WordId.
", ".$SynonymId.
")");
469 # report to caller number of new synonyms added 482 $WordId = $this->GetWordId($Word);
485 if ($WordId !== NULL)
487 # if no specific synonyms provided 488 if ($Synonyms === NULL)
490 # remove all synonyms for word 491 $this->DB->Query(
"DELETE FROM SearchWordSynonyms" 492 .
" WHERE WordIdA = '".$WordId.
"'" 493 .
" OR WordIdB = '".$WordId.
"'");
497 # for each specified synonym 498 foreach ($Synonyms as $Synonym)
500 # look up ID for synonym 501 $SynonymId = $this->GetWordId($Synonym);
503 # if synonym ID was found 504 if ($SynonymId !== NULL)
506 # delete synonym entry 507 $this->DB->Query(
"DELETE FROM SearchWordSynonyms" 508 .
" WHERE (WordIdA = '".$WordId.
"'" 509 .
" AND WordIdB = '".$SynonymId.
"')" 510 .
" OR (WordIdB = '".$WordId.
"'" 511 .
" AND WordIdA = '".$SynonymId.
"')");
523 $this->DB->Query(
"DELETE FROM SearchWordSynonyms");
533 # assume no synonyms will be found 536 # look up ID for word 537 $WordId = $this->GetWordId($Word);
539 # if word ID was found 540 if ($WordId !== NULL)
542 # look up IDs of all synonyms for this word 543 $this->DB->Query(
"SELECT WordIdA, WordIdB FROM SearchWordSynonyms" 544 .
" WHERE WordIdA = ".$WordId
545 .
" OR WordIdB = ".$WordId);
546 $SynonymIds = array();
547 while ($Record = $this->DB->FetchRow)
549 $SynonymIds[] = ($Record[
"WordIdA"] == $WordId)
550 ? $Record[
"WordIdB"] : $Record[
"WordIdA"];
553 # for each synonym ID 554 foreach ($SynonymIds as $SynonymId)
556 # look up synonym word and add to synonym list 557 $Synonyms[] = $this->GetWord($SynonymId);
561 # return synonyms to caller 571 # assume no synonyms will be found 572 $SynonymList = array();
574 # for each synonym ID pair 576 $OurDB->Query(
"SELECT WordIdA, WordIdB FROM SearchWordSynonyms");
577 while ($Record = $OurDB->FetchRow())
580 $Word = $this->GetWord($Record[
"WordIdA"]);
581 $Synonym = $this->GetWord($Record[
"WordIdB"]);
583 # if we do not already have an entry for the word 584 # or synonym is not listed for this word 585 if (!isset($SynonymList[$Word])
586 || !in_array($Synonym, $SynonymList[$Word]))
588 # add entry for synonym 589 $SynonymList[$Word][] = $Synonym;
592 # if we do not already have an entry for the synonym 593 # or word is not listed for this synonym 594 if (!isset($SynonymList[$Synonym])
595 || !in_array($Word, $SynonymList[$Synonym]))
598 $SynonymList[$Synonym][] = $Word;
603 # (this loop removes reciprocal duplicates) 604 foreach ($SynonymList as $Word => $Synonyms)
606 # for each synonym for that word 607 foreach ($Synonyms as $Synonym)
609 # if synonym has synonyms and word is one of them 610 if (isset($SynonymList[$Synonym])
611 && isset($SynonymList[$Word])
612 && in_array($Word, $SynonymList[$Synonym])
613 && in_array($Synonym, $SynonymList[$Word]))
615 # if word has less synonyms than synonym 616 if (count($SynonymList[$Word])
617 < count($SynonymList[$Synonym]))
619 # remove synonym from synonym list for word 620 $SynonymList[$Word] = array_diff(
621 $SynonymList[$Word], array($Synonym));
623 # if no synonyms left for word 624 if (!count($SynonymList[$Word]))
626 # remove empty synonym list for word 627 unset($SynonymList[$Word]);
632 # remove word from synonym list for synonym 633 $SynonymList[$Synonym] = array_diff(
634 $SynonymList[$Synonym], array($Word));
636 # if no synonyms left for word 637 if (!count($SynonymList[$Synonym]))
639 # remove empty synonym list for word 640 unset($SynonymList[$Synonym]);
647 # sort array alphabetically (just for convenience) 648 foreach ($SynonymList as $Word => $Synonyms)
650 asort($SynonymList[$Word]);
654 # return 2D array of synonyms to caller 665 # remove all existing synonyms 668 # for each synonym entry passed in 669 foreach ($SynonymList as $Word => $Synonyms)
671 # add synonyms for word 686 # asssume no synonyms will be added 689 # read in contents of file 690 $Lines = file($FileName, FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES);
692 # if file contained lines 695 # for each line of file 696 foreach ($Lines as $Line)
698 # if line is not a comment 699 if (!preg_match(
"/[\s]*#/", $Line))
701 # split line into words 702 $Words = preg_split(
"/[\s,]+/", $Line);
705 if (count($Words) > 1)
707 # separate out word and synonyms 708 $Word = array_shift($Words);
717 # return count of synonyms added to caller 731 foreach ($Results as $ItemType => $ItemScores)
733 $FlatScores += $ItemScores;
748 $DB->Query(
"SELECT * FROM SearchItemTypes");
749 $ItemTypes =
$DB->FetchColumn(
"ItemType",
"ItemId");
752 foreach ($Results as $ItemId => $ItemScore)
754 $ItemType = $ItemTypes[$ItemId];
755 $SplitScores[$ItemType][$ItemId] = $ItemScore;
761 # ---- PRIVATE INTERFACE ------------------------------------------------- 774 private $ExcludedTermCount;
777 private $InclusiveTermCount;
778 private $RequiredTermCount;
779 private $RequiredTermCounts;
780 private $SearchTermList;
781 private $WordCountAdded;
787 # ---- private methods (searching) 796 private function RawSearch($SearchParams)
798 # retrieve search strings 799 $SearchStrings = $SearchParams->GetSearchStrings();
800 $KeywordSearchStrings = $SearchParams->GetKeywordSearchStrings();
802 # add keyword searches (if any) to fielded searches 803 if (count($KeywordSearchStrings))
805 $SearchStrings[self::KEYWORD_FIELD_ID] = $KeywordSearchStrings;
808 # normalize search strings 809 $NormalizedSearchStrings = array();
810 foreach ($SearchStrings as $FieldId => $SearchStringArray)
812 if (!is_array($SearchStringArray))
814 $SearchStringArray = array($SearchStringArray);
816 foreach ($SearchStringArray as $String)
818 $String = trim($String);
821 $NormalizedSearchStrings[$FieldId][] = $String;
825 $SearchStrings = $NormalizedSearchStrings;
827 # if we have strings to search for 828 if (count($SearchStrings))
831 $Scores = $this->SearchAcrossFields(
832 $SearchStrings, $SearchParams->Logic());
836 foreach ($SearchParams->GetSubgroups() as $Subgroup)
838 # perform subgroup search 839 $NewScores = $this->RawSearch($Subgroup);
841 # added subgroup search scores to previous scores as appropriate 844 $Scores = $this->CombineScores(
845 $Scores, $NewScores, $SearchParams->Logic());
849 $Scores = $NewScores;
852 if (isset($NewScores))
854 $this->
DMsg(2,
"Have ".count($Scores)
855 .
" results after subgroup processing");
858 # pare down results to just allowed item types (if specified) 859 if ($SearchParams->ItemTypes())
861 $AllowedItemTypes = $SearchParams->ItemTypes();
862 foreach ($Scores as $ItemId => $Score)
864 if (!in_array($this->GetItemType($ItemId), $AllowedItemTypes))
866 unset($Scores[$ItemId]);
869 $this->
DMsg(3,
"Have ".count($Scores)
870 .
" results after paring to allowed item types");
873 # return search results to caller 874 return isset($Scores) ? $Scores : array();
884 private function CombineScores($ScoresA, $ScoresB, $Logic)
889 foreach ($ScoresB as $ItemId => $Score)
891 if (isset($Scores[$ItemId]))
893 $Scores[$ItemId] += $Score;
897 $Scores[$ItemId] = $Score;
904 foreach ($ScoresA as $ItemId => $Score)
906 if (isset($ScoresB[$ItemId]))
908 $Scores[$ItemId] = $Score + $ScoresB[$ItemId];
924 private function SearchAcrossFields($SearchStrings, $Logic)
926 # start by assuming no search will be done 930 $this->ExcludedTermCount = 0;
931 $this->InclusiveTermCount = 0;
932 $this->RequiredTermCount = 0;
933 $this->RequiredTermCounts = array();
936 $NeedComparisonSearch = FALSE;
937 foreach ($SearchStrings as $FieldId => $SearchStringArray)
939 # for each search string for this field 940 foreach ($SearchStringArray as $SearchString)
942 # if field is keyword or field is text and does not look 943 # like comparison match 944 $NotComparisonSearch = !preg_match(
945 self::COMPARISON_OPERATOR_PATTERN, $SearchString);
946 if (($FieldId == self::KEYWORD_FIELD_ID)
947 || (isset($this->FieldInfo[$FieldId])
948 && ($this->FieldInfo[$FieldId][
"FieldType"]
949 == self::FIELDTYPE_TEXT)
950 && $NotComparisonSearch))
952 if ($FieldId == self::KEYWORD_FIELD_ID)
954 $this->
DMsg(0,
"Performing keyword search for string \"" 955 .$SearchString.
"\"");
959 $this->
DMsg(0,
"Searching text field " 960 .$FieldId.
" for string \"".$SearchString.
"\"");
963 # normalize text and split into words 965 $this->ParseSearchStringForWords($SearchString, $Logic);
967 # calculate scores for matching items 968 if (count($Words[$FieldId]))
970 $Scores = $this->SearchForWords(
971 $Words[$FieldId], $FieldId, $Scores);
972 $this->
DMsg(3,
"Have " 973 .count($Scores).
" results after word search");
977 $Phrases[$FieldId] = $this->ParseSearchStringForPhrases(
978 $SearchString, $Logic);
981 if (count($Phrases[$FieldId]))
983 $Scores = $this->SearchForPhrases(
984 $Phrases[$FieldId], $Scores, $FieldId, TRUE, FALSE);
985 $this->
DMsg(3,
"Have " 986 .count($Scores).
" results after phrase search");
991 # set flag to indicate possible comparison search candidate found 992 $NeedComparisonSearch = TRUE;
997 # perform comparison searches 998 if ($NeedComparisonSearch)
1000 $Scores = $this->SearchForComparisonMatches(
1001 $SearchStrings, $Logic, $Scores);
1002 $this->
DMsg(3,
"Have ".count($Scores).
" results after comparison search");
1005 # if no results found, no required terms, and exclusions specified 1006 if ((count($Scores) == 0) &&
1007 ($this->RequiredTermCount == 0) &&
1008 ($this->ExcludedTermCount > 0) )
1010 # determine which item types are implicated for keyword searches 1011 $KeywordItemTypes = [];
1012 foreach ($this->FieldInfo as $FieldId => $Info)
1014 if ($Info[
"InKeywordSearch"])
1016 $KeywordItemTypes = array_merge(
1018 $Info[
"ItemTypes"]);
1021 $KeywordItemTypes = array_unique($KeywordItemTypes);
1023 # determine what item types were in use for the fields we 1026 foreach ($SearchStrings as $FieldId => $Info)
1028 $MyTypes = ($FieldId == self::KEYWORD_FIELD_ID) ?
1030 $this->FieldInfo[$FieldId][
"ItemTypes"];
1032 $FieldTypes = array_merge(
1033 $FieldTypes, $MyTypes);
1035 $FieldTypes = array_unique($FieldTypes);
1037 # load all records for these field types 1038 $Scores = $this->LoadScoresForAllRecords($FieldTypes);
1041 # if search results found 1044 # for each search text string 1045 foreach ($SearchStrings as $FieldId => $SearchStringArray)
1047 # for each search string for this field 1048 foreach ($SearchStringArray as $SearchString)
1051 if (($FieldId == self::KEYWORD_FIELD_ID)
1052 || (isset($this->FieldInfo[$FieldId])
1053 && ($this->FieldInfo[$FieldId][
"FieldType"]
1054 == self::FIELDTYPE_TEXT)))
1056 # if there are words in search text 1057 if (isset($Words[$FieldId]))
1059 # handle any excluded words 1060 $Scores = $this->FilterOnExcludedWords(
1061 $Words[$FieldId], $Scores, $FieldId);
1064 # handle any excluded phrases 1065 if (isset($Phrases[$FieldId]))
1067 $Scores = $this->SearchForPhrases(
1068 $Phrases[$FieldId], $Scores,
1069 $FieldId, FALSE, TRUE);
1073 $this->
DMsg(3,
"Have ".count($Scores)
1074 .
" results after processing exclusions");
1077 # strip off any results that don't contain required words 1078 $Scores = $this->FilterOnRequiredWords($Scores);
1081 # return search result scores to caller 1094 private function SearchForWords($Words, $FieldId, $Scores = NULL)
1098 # start with empty search result scores list if none passed in 1099 if ($Scores == NULL)
1105 foreach ($Words as $Word => $Flags)
1108 if ($FieldId == self::KEYWORD_FIELD_ID)
1110 $this->
DMsg(2,
"Performing keyword search for word \"".$Word.
"\"");
1114 $this->
DMsg(2,
"Searching for word \"".$Word.
"\" in field ".$FieldId);
1117 # if word is not excluded 1118 if (!($Flags & self::WORD_EXCLUDED))
1120 # look up record ID for word 1121 $this->
DMsg(2,
"Looking up word \"".$Word.
"\"");
1122 $WordId = $this->GetWordId($Word);
1125 if ($WordId !== NULL)
1127 # look up counts for word 1128 $DB->Query(
"SELECT ItemId,Count FROM SearchWordCounts " 1129 .
"WHERE WordId = ".$WordId
1130 .
" AND FieldId = ".$FieldId);
1131 $Counts =
$DB->FetchColumn(
"Count",
"ItemId");
1133 # if synonym support is enabled 1134 if ($this->SynonymsEnabled)
1136 # look for any synonyms 1137 $DB->Query(
"SELECT WordIdA, WordIdB" 1138 .
" FROM SearchWordSynonyms" 1139 .
" WHERE WordIdA = ".$WordId
1140 .
" OR WordIdB = ".$WordId);
1142 # if synonyms were found 1143 if (
$DB->NumRowsSelected())
1145 # retrieve synonym IDs 1146 $SynonymIds = array();
1147 while ($Record =
$DB->FetchRow())
1149 $SynonymIds[] = ($Record[
"WordIdA"] == $WordId)
1150 ? $Record[
"WordIdB"]
1151 : $Record[
"WordIdA"];
1155 foreach ($SynonymIds as $SynonymId)
1157 # retrieve counts for synonym 1158 $DB->Query(
"SELECT ItemId,Count" 1159 .
" FROM SearchWordCounts" 1160 .
" WHERE WordId = ".$SynonymId
1161 .
" AND FieldId = ".$FieldId);
1162 $SynonymCounts =
$DB->FetchColumn(
"Count",
"ItemId");
1165 foreach ($SynonymCounts as $ItemId => $Count)
1167 # adjust count because it's a synonym 1168 $AdjustedCount = ceil($Count / 2);
1170 # add count to existing counts 1171 if (isset($Counts[$ItemId]))
1173 $Counts[$ItemId] += $AdjustedCount;
1177 $Counts[$ItemId] = $AdjustedCount;
1185 # if stemming is enabled 1186 if ($this->StemmingEnabled)
1188 # retrieve word stem 1189 $Stem = PorterStemmer::Stem($Word);
1191 # if stem was different from word 1195 $this->
DMsg(2,
"Looking up stem \"".$Stem.
"\"");
1196 $StemId = $this->GetStemId($Stem);
1198 # if ID found for stem 1199 if ($StemId !== NULL)
1201 # retrieve counts for stem 1202 $DB->Query(
"SELECT ItemId,Count" 1203 .
" FROM SearchWordCounts" 1204 .
" WHERE WordId = ".$StemId
1205 .
" AND FieldId = ".$FieldId);
1206 $StemCounts =
$DB->FetchColumn(
"Count",
"ItemId");
1209 foreach ($StemCounts as $ItemId => $Count)
1211 # adjust count because it's a stem 1212 $AdjustedCount = ceil($Count / 2);
1214 # add count to existing counts 1215 if (isset($Counts[$ItemId]))
1217 $Counts[$ItemId] += $AdjustedCount;
1221 $Counts[$ItemId] = $AdjustedCount;
1228 # if counts were found 1232 foreach ($Counts as $ItemId => $Count)
1234 # if word flagged as required 1235 if ($Flags & self::WORD_REQUIRED)
1237 # increment required word count for record 1238 if (isset($this->RequiredTermCounts[$ItemId]))
1240 $this->RequiredTermCounts[$ItemId]++;
1244 $this->RequiredTermCounts[$ItemId] = 1;
1248 # add to item record score 1249 if (isset($Scores[$ItemId]))
1251 $Scores[$ItemId] += $Count;
1255 $Scores[$ItemId] = $Count;
1262 # return basic scores to caller 1273 private function ParseSearchStringForPhrases($SearchString, $Logic)
1275 # split into chunks delimited by double quote marks 1276 $Pieces = explode(
"\"", $SearchString); #
" 1278 # for each pair of chunks 1281 while ($Index < count($Pieces)) 1283 # grab phrase from chunk 1284 $Phrase = trim(addslashes($Pieces[$Index - 1])); 1285 $Flags = self::WORD_PRESENT; 1287 # grab first character of phrase 1288 $FirstChar = substr($Pieces[$Index - 2], -1); 1290 # set flags to reflect any option characters 1291 if ($FirstChar == "-
") 1293 $Flags |= self::WORD_EXCLUDED; 1294 if (!isset($Phrases[$Phrase])) 1296 $this->ExcludedTermCount++; 1301 if ((($Logic == "AND
") 1302 && ($FirstChar != "~
")) 1303 || ($FirstChar == "+
")) 1305 $Flags |= self::WORD_REQUIRED; 1306 if (!isset($Phrases[$Phrase])) 1308 $this->RequiredTermCount++; 1311 if (!isset($Phrases[$Phrase])) 1313 $this->InclusiveTermCount++; 1314 $this->SearchTermList[] = $Phrase; 1317 $Phrases[$Phrase] = $Flags; 1319 # move to next pair of chunks 1323 # return phrases to caller 1332 protected function SearchFieldForPhrases($FieldId, $Phrase) 1349 private function SearchForPhrases($Phrases, $Scores, $FieldId,
1350 $ProcessNonExcluded = TRUE, $ProcessExcluded = TRUE)
1352 # if phrases are found 1353 if (count($Phrases) > 0)
1355 # if this is a keyword search 1356 if ($FieldId == self::KEYWORD_FIELD_ID)
1359 foreach ($this->FieldInfo as $KFieldId => $Info)
1361 # if field is marked to be included in keyword searches 1362 if ($Info[
"InKeywordSearch"])
1364 # call ourself with that field 1365 $Scores = $this->SearchForPhrases(
1366 $Phrases, $Scores, $KFieldId,
1367 $ProcessNonExcluded, $ProcessExcluded);
1374 foreach ($Phrases as $Phrase => $Flags)
1376 $this->
DMsg(2,
"Searching for phrase '".$Phrase
1377 .
"' in field ".$FieldId);
1379 # if phrase flagged as excluded and we are doing excluded 1380 # phrases or phrase flagged as non-excluded and we 1381 # are doing non-excluded phrases 1382 if (($ProcessExcluded && ($Flags & self::WORD_EXCLUDED))
1383 || ($ProcessNonExcluded && !($Flags & self::WORD_EXCLUDED)))
1385 # initialize score list if necessary 1386 if ($Scores === NULL) { $Scores = array(); }
1388 # retrieve list of items that contain phrase 1392 # for each item that contains phrase 1393 foreach ($ItemIds as $ItemId)
1395 # if we are doing excluded phrases and phrase 1396 # is flagged as excluded 1397 if ($ProcessExcluded && ($Flags & self::WORD_EXCLUDED))
1399 # knock item off of list 1400 unset($Scores[$ItemId]);
1402 elseif ($ProcessNonExcluded)
1404 # calculate phrase value based on number of 1405 # words and field weight 1406 $PhraseScore = count(preg_split(
"/[\s]+/",
1407 $Phrase, -1, PREG_SPLIT_NO_EMPTY))
1408 * $this->FieldInfo[$FieldId][
"Weight"];
1409 $this->
DMsg(2,
"Phrase score is ".$PhraseScore);
1411 # bump up item record score 1412 if (isset($Scores[$ItemId]))
1414 $Scores[$ItemId] += $PhraseScore;
1418 $Scores[$ItemId] = $PhraseScore;
1421 # if phrase flagged as required 1422 if ($Flags & self::WORD_REQUIRED)
1424 # increment required word count for record 1425 if (isset($this->RequiredTermCounts[$ItemId]))
1427 $this->RequiredTermCounts[$ItemId]++;
1431 $this->RequiredTermCounts[$ItemId] = 1;
1441 # return updated scores to caller 1453 private function FilterOnExcludedWords($Words, $Scores, $FieldId)
1458 foreach ($Words as $Word => $Flags)
1460 # if word flagged as excluded 1461 if ($Flags & self::WORD_EXCLUDED)
1463 # look up record ID for word 1464 $WordId = $this->GetWordId($Word);
1467 if ($WordId !== NULL)
1469 # look up counts for word 1470 $DB->Query(
"SELECT ItemId FROM SearchWordCounts " 1471 .
"WHERE WordId=${WordId} AND FieldId=${FieldId}");
1474 while ($Record =
$DB->FetchRow())
1476 # if item record is in score list 1477 $ItemId = $Record[
"ItemId"];
1478 if (isset($Scores[$ItemId]))
1480 # remove item record from score list 1481 $this->
DMsg(3,
"Filtering out item ".$ItemId
1482 .
" because it contained word \"".$Word.
"\"");
1483 unset($Scores[$ItemId]);
1490 # returned filtered score list to caller 1499 private function FilterOnRequiredWords($Scores)
1501 # if there were required words 1502 if ($this->RequiredTermCount > 0)
1505 foreach ($Scores as $ItemId => $Score)
1507 # if item does not meet required word count 1508 if (!isset($this->RequiredTermCounts[$ItemId])
1509 || ($this->RequiredTermCounts[$ItemId]
1510 < $this->RequiredTermCount))
1513 $this->
DMsg(4,
"Filtering out item ".$ItemId
1514 .
" because it didn't have required word count of " 1515 .$this->RequiredTermCount
1516 .(isset($this->RequiredTermCounts[$ItemId])
1518 .$this->RequiredTermCounts[$ItemId]
1521 unset($Scores[$ItemId]);
1526 # return filtered list to caller 1542 private function CleanScores($Scores, $StartingResult, $NumberOfResults,
1543 $SortByField, $SortDescending)
1545 # perform any requested filtering 1546 $this->
DMsg(0,
"Have ".count($Scores).
" results before filter callbacks");
1549 # save total number of results available 1550 $this->NumberOfResultsAvailable = count($Scores);
1552 # sort search scores into item type bins 1553 $NewScores = array();
1554 foreach ($Scores as $Id => $Score)
1556 $ItemType = $this->GetItemType($Id);
1557 if ($ItemType !== NULL)
1559 $NewScores[$ItemType][$Id] = $Score;
1562 $Scores = $NewScores;
1564 # for each item type 1565 $NewSortByField = array();
1566 $NewSortDescending = array();
1567 foreach ($Scores as $ItemType => $TypeScores)
1569 # normalize sort field parameter 1570 $NewSortByField[$ItemType] = !is_array($SortByField) ? $SortByField
1571 : (isset($SortByField[$ItemType])
1572 ? $SortByField[$ItemType] : NULL);
1574 # normalize sort direction parameter 1575 $NewSortDescending[$ItemType] = !is_array($SortDescending) ? $SortDescending
1576 : (isset($SortDescending[$ItemType])
1577 ? $SortDescending[$ItemType] : TRUE);
1579 $SortByField = $NewSortByField;
1580 $SortDescending = $NewSortDescending;
1582 # for each item type 1583 foreach ($Scores as $ItemType => $TypeScores)
1585 # save number of results 1586 $this->NumberOfResultsPerItemType[$ItemType] = count($TypeScores);
1588 # if no sorting field specified 1589 if ($SortByField[$ItemType] === NULL)
1591 # sort result list by score 1592 if ($SortDescending[$ItemType])
1594 arsort($Scores[$ItemType], SORT_NUMERIC);
1598 asort($Scores[$ItemType], SORT_NUMERIC);
1603 # get list of item IDs in sorted order 1604 $SortedIds = $this->GetItemIdsSortedByField($ItemType,
1605 $SortByField[$ItemType], $SortDescending[$ItemType]);
1607 # if we have sorted item IDs 1608 if (count($SortedIds) && count($TypeScores))
1610 # strip sorted ID list down to those that appear in search results 1611 $SortedIds = array_intersect($SortedIds,
1612 array_keys($TypeScores));
1614 # rebuild score list in sorted order 1615 $NewScores = array();
1616 foreach ($SortedIds as $Id)
1618 $NewScores[$Id] = $TypeScores[$Id];
1620 $Scores[$ItemType] = $NewScores;
1624 # sort result list by score 1625 arsort($Scores[$ItemType], SORT_NUMERIC);
1629 # if subset of scores requested 1630 if (($StartingResult > 0) || ($NumberOfResults < PHP_INT_MAX))
1632 # trim scores back to requested subset 1633 $ScoresKeys = array_slice(array_keys($Scores[$ItemType]),
1634 $StartingResult, $NumberOfResults);
1635 $NewScores = array();
1636 foreach ($ScoresKeys as $Key)
1638 $NewScores[$Key] = $Scores[$ItemType][$Key];
1640 $Scores[$ItemType] = $NewScores;
1644 # returned cleaned search result scores list to caller 1655 # if filter functions have been set 1656 if (isset($this->FilterFuncs))
1659 foreach ($Scores as $ItemId => $Score)
1661 # for each filter function 1662 foreach ($this->FilterFuncs as $FuncName)
1664 # if filter function return TRUE for item 1665 if (call_user_func($FuncName, $ItemId))
1668 $this->
DMsg(2,
"Filter callback <i>".$FuncName
1669 .
"</i> rejected item ".$ItemId);
1670 unset($Scores[$ItemId]);
1672 # bail out of filter func loop 1679 # return filtered list to caller 1692 private function SearchForComparisonMatches($SearchStrings, $Logic, $Scores)
1696 foreach ($SearchStrings as $SearchFieldId => $SearchStringArray)
1698 # if field is not keyword 1699 if ($SearchFieldId != self::KEYWORD_FIELD_ID)
1701 # for each search string for this field 1702 foreach ($SearchStringArray as $SearchString)
1704 # look for comparison operators 1705 $FoundOperator = preg_match(
1706 self::COMPARISON_OPERATOR_PATTERN,
1707 $SearchString, $Matches);
1709 # if a comparison operator was found 1710 # or this is a field type that is always a comparison search 1711 if ($FoundOperator ||
1712 ($this->FieldInfo[$SearchFieldId][
"FieldType"]
1713 != self::FIELDTYPE_TEXT))
1715 # determine value to compare against 1716 $Value = trim(preg_replace(
1717 self::COMPARISON_OPERATOR_PATTERN,
'\2',
1720 # if no comparison operator was found 1721 if (!$FoundOperator)
1723 # assume comparison is equality 1724 $Operators[$Index] =
"=";
1728 # use operator from comparison match 1729 $Operators[$Index] = $Matches[1];
1732 # if operator was found 1733 if (isset($Operators[$Index]))
1736 $Values[$Index] = $Value;
1739 $FieldIds[$Index] = $SearchFieldId;
1740 $this->
DMsg(3,
"Added comparison (field = <i>" 1741 .$FieldIds[$Index].
"</i> op = <i>" 1742 .$Operators[$Index].
"</i> val = <i>" 1743 .$Values[$Index].
"</i>)");
1745 # move to next comparison array entry 1753 # if comparisons found 1754 if (isset($Operators))
1756 # perform comparisons on fields and gather results 1757 $Results = $this->SearchFieldsForComparisonMatches(
1758 $FieldIds, $Operators, $Values, $Logic);
1760 # if search logic is set to AND 1761 if ($Logic ==
"AND")
1763 # if results were found 1764 if (count($Results))
1766 # if there were no prior results and no terms for keyword search 1767 if ((count($Scores) == 0) && ($this->InclusiveTermCount == 0))
1769 # add all results to scores 1770 foreach ($Results as $ItemId)
1772 $Scores[$ItemId] = 1;
1777 # remove anything from scores that is not part of results 1778 foreach ($Scores as $ItemId => $Score)
1780 if (in_array($ItemId, $Results) == FALSE)
1782 unset($Scores[$ItemId]);
1795 # add result items to scores 1796 if ($Scores === NULL) { $Scores = array(); }
1797 foreach ($Results as $ItemId)
1799 if (isset($Scores[$ItemId]))
1801 $Scores[$ItemId] += 1;
1805 $Scores[$ItemId] = 1;
1811 # return results to caller 1822 private function SetDebugLevel($SearchStrings)
1824 # if search info is an array 1825 if (is_array($SearchStrings))
1827 # for each array element 1828 foreach ($SearchStrings as $FieldId => $SearchStringArray)
1830 # if element is an array 1831 if (is_array($SearchStringArray))
1833 # for each array element 1834 foreach ($SearchStringArray as $Index => $SearchString)
1836 # pull out search string if present 1837 $SearchStrings[$FieldId][$Index] =
1838 $this->ExtractDebugLevel($SearchString);
1843 # pull out search string if present 1844 $SearchStrings[$FieldId] =
1845 $this->ExtractDebugLevel($SearchStringArray);
1851 # pull out search string if present 1852 $SearchStrings = $this->ExtractDebugLevel($SearchStrings);
1855 # return new search info to caller 1856 return $SearchStrings;
1865 private function ExtractDebugLevel($SearchString)
1867 # if search string contains debug level indicator 1868 if (strstr($SearchString,
"DBUGLVL="))
1870 # remove indicator and set debug level 1871 $Level = preg_replace(
"/^\\s*DBUGLVL=([1-9]{1,2}).*/",
"\\1", $SearchString);
1875 $this->
DMsg(0,
"Setting debug level to ".$Level);
1876 $SearchString = preg_replace(
"/\s*DBUGLVL=${Level}\s*/",
"",
1881 # return (possibly) modified search string to caller 1882 return $SearchString;
1890 private function LoadScoresForAllRecords($ItemTypes)
1892 # if no item types were provided return an empty array 1893 if (count($ItemTypes)==0)
1898 # get all the ItemIds belonging to the given types 1899 $this->DB->Query(
"SELECT ".$this->ItemIdFieldName.
" AS ItemId" 1900 .
" FROM ".$this->ItemTableName
1901 .
" WHERE ".$this->ItemTypeFieldName.
" IN(".implode(
",", $ItemTypes).
")");
1903 # return array with all scores to caller 1904 return array_fill_keys($this->DB->FetchColumn(
"ItemId"), 1);
1907 # ---- private methods (search DB building) 1916 private function UpdateWordCount($Word, $ItemId, $FieldId, $Weight = 1)
1918 # retrieve ID for word 1919 $WordIds[] = $this->GetWordId($Word, TRUE);
1921 # if stemming is enabled and word looks appropriate for stemming 1922 if ($this->StemmingEnabled && !is_numeric($Word))
1924 # retrieve stem of word 1925 $Stem = PorterStemmer::Stem($Word, TRUE);
1927 # if stem is different 1930 # retrieve ID for stem of word 1931 $WordIds[] = $this->GetStemId($Stem, TRUE);
1935 # for word and stem of word 1936 foreach ($WordIds as $WordId)
1938 # if word count already added to database 1939 if (isset($this->WordCountAdded[$WordId][$FieldId]))
1942 $this->DB->Query(
"UPDATE SearchWordCounts SET Count=Count+".$Weight
1943 .
" WHERE WordId=".$WordId
1944 .
" AND ItemId=".$ItemId
1945 .
" AND FieldId=".$FieldId);
1949 # add word count to DB 1950 $this->DB->Query(
"INSERT INTO SearchWordCounts" 1951 .
" (WordId, ItemId, FieldId, Count) VALUES" 1952 .
" (".$WordId.
", ".$ItemId.
", ".$FieldId.
", ".$Weight.
")");
1954 # remember that we added count for this word 1955 $this->WordCountAdded[$WordId][$FieldId] = TRUE;
1958 # decrease weight for stem 1959 $Weight = ceil($Weight / 2);
1971 throw Exception(
"GetFieldContent() not implemented.");
1983 private function RecordSearchInfoForText(
1984 $ItemId, $FieldId, $Weight, $Text, $IncludeInKeyword)
1987 $Words = $this->ParseSearchStringForWords($Text,
"OR", TRUE);
1989 # if there was text left after parsing 1990 if (count($Words) > 0)
1993 foreach ($Words as $Word => $Flags)
1995 # update count for word 1996 $this->UpdateWordCount($Word, $ItemId, $FieldId);
1998 # if text should be included in keyword searches 1999 if ($IncludeInKeyword)
2001 # update keyword field count for word 2002 $this->UpdateWordCount(
2003 $Word, $ItemId, self::KEYWORD_FIELD_ID, $Weight);
2009 # ---- common private methods (used in both searching and DB build) 2021 private function ParseSearchStringForWords(
2022 $SearchString, $Logic, $IgnorePhrases = FALSE)
2024 # strip off any surrounding whitespace 2025 $Text = trim($SearchString);
2027 # define phrase and group search patterns separately, so that we can 2028 # later replace them easily if necessary 2029 $PhraseSearchPattern =
"/\"[^\"]*\"/";
2030 $GroupSearchPattern =
"/\\([^)]*\\)/";
2032 # set up search string normalization replacement strings (NOTE: these 2033 # are performed in sequence, so the order IS SIGNIFICANT) 2034 $ReplacementPatterns = array(
2035 #
get rid of possessive plurals
2036 "/'s[^a-z0-9\\-+~]+/i" =>
" ",
2037 #
get rid of single quotes / apostrophes
2039 #
get rid of phrases
2040 $PhraseSearchPattern =>
" ",
2042 $GroupSearchPattern =>
" ",
2043 # convert everything but alphanumerics and minus/plus/tilde to a space
2044 "/[^a-z0-9\\-+~]+/i" =>
"\\1 ",
2045 # truncate any runs of minus/plus/tilde to just the first
char 2046 "/([~+-])[~+-]+/" =>
"\\1",
2047 # convert two alphanumerics segments separated by a minus into
2048 # both separate words and a single combined word
2049 "/([~+-]?)([a-z0-9]+)-([a-z0-9]+)/i" =>
"\\1\\2 \\1\\3 \\1\\2\\3",
2050 # convert minus/plus/tilde preceded by anything but whitespace to a space
2051 "/([^\\s])[~+-]+/i" =>
"\\1 ",
2052 # convert minus/plus/tilde followed by whitespace to a space
2053 "/[~+-]+\\s/i" =>
" ",
2054 # convert multiple spaces to one space
2058 # if we are supposed to ignore phrasing (series of words in quotes) 2059 # and grouping (series of words surrounded by parens) 2062 # switch phrase removal to double quote removal 2063 # and switch group removal to paren removal 2064 foreach ($ReplacementPatterns as $Pattern => $Replacement)
2066 if ($Pattern == $PhraseSearchPattern)
2070 elseif ($Pattern == $GroupSearchPattern)
2072 $Pattern =
"/[\(\)]+/";
2074 $NewReplacementPatterns[$Pattern] = $Replacement;
2076 $ReplacementPatterns = $NewReplacementPatterns;
2079 # remove punctuation from text and normalize whitespace 2080 $Text = preg_replace(array_keys($ReplacementPatterns),
2081 $ReplacementPatterns, $Text);
2082 $this->
DMsg(2,
"Normalized search string is \"".$Text.
"\"");
2084 # convert text to lower case 2085 $Text = strtolower($Text);
2087 # strip off any extraneous whitespace 2088 $Text = trim($Text);
2090 # start with an empty array 2093 # if we have no words left after parsing 2094 if (strlen($Text) != 0)
2097 foreach (explode(
" ", $Text) as $Word)
2099 # grab first character of word 2100 $FirstChar = substr($Word, 0, 1);
2102 # strip off option characters and set flags appropriately 2103 $Flags = self::WORD_PRESENT;
2104 if ($FirstChar ==
"-")
2106 $Word = substr($Word, 1);
2107 $Flags |= self::WORD_EXCLUDED;
2108 if (!isset($Words[$Word]))
2110 $this->ExcludedTermCount++;
2115 if ($FirstChar ==
"~")
2117 $Word = substr($Word, 1);
2119 elseif (($Logic ==
"AND")
2120 || ($FirstChar ==
"+"))
2122 if ($FirstChar ==
"+")
2124 $Word = substr($Word, 1);
2126 $Flags |= self::WORD_REQUIRED;
2127 if (!isset($Words[$Word]))
2129 $this->RequiredTermCount++;
2132 if (!isset($Words[$Word]))
2134 $this->InclusiveTermCount++;
2135 $this->SearchTermList[] = $Word;
2139 # store flags to indicate word found 2140 $Words[$Word] = $Flags;
2141 $this->
DMsg(3,
"Word identified (".$Word.
")");
2145 # return normalized words to caller 2156 private function GetWordId($Word, $AddIfNotFound = FALSE)
2158 static $WordIdCache;
2160 # if word was in ID cache 2161 if (isset($WordIdCache[$Word]))
2164 $WordId = $WordIdCache[$Word];
2168 # look up ID in database 2169 $WordId = $this->DB->Query(
"SELECT WordId" 2170 .
" FROM SearchWords" 2171 .
" WHERE WordText='".addslashes($Word).
"'",
2174 # if ID was not found and caller requested it be added 2175 if (($WordId === NULL) && $AddIfNotFound)
2177 # add word to database 2178 $this->DB->Query(
"INSERT INTO SearchWords (WordText)" 2179 .
" VALUES ('".addslashes(strtolower($Word)).
"')");
2181 # get ID for newly added word 2182 $WordId = $this->DB->LastInsertId();
2186 $WordIdCache[$Word] = $WordId;
2189 # return ID to caller 2200 private function GetStemId($Stem, $AddIfNotFound = FALSE)
2202 static $StemIdCache;
2204 # if stem was in ID cache 2205 if (isset($StemIdCache[$Stem]))
2208 $StemId = $StemIdCache[$Stem];
2212 # look up ID in database 2213 $StemId = $this->DB->Query(
"SELECT WordId" 2214 .
" FROM SearchStems" 2215 .
" WHERE WordText='".addslashes($Stem).
"'",
2218 # if ID was not found and caller requested it be added 2219 if (($StemId === NULL) && $AddIfNotFound)
2221 # add stem to database 2222 $this->DB->Query(
"INSERT INTO SearchStems (WordText)" 2223 .
" VALUES ('".addslashes(strtolower($Stem)).
"')");
2225 # get ID for newly added stem 2226 $StemId = $this->DB->LastInsertId();
2229 # adjust from DB ID value to stem ID value 2230 $StemId += self::STEM_ID_OFFSET;
2233 $StemIdCache[$Stem] = $StemId;
2236 # return ID to caller 2245 private function GetWord($WordId)
2249 # if word was in cache 2250 if (isset($WordCache[$WordId]))
2252 # use word from cache 2253 $Word = $WordCache[$WordId];
2257 # adjust search location and word ID if word is stem 2258 $TableName =
"SearchWords";
2259 if ($WordId >= self::STEM_ID_OFFSET)
2261 $TableName =
"SearchStems";
2262 $WordId -= self::STEM_ID_OFFSET;
2265 # look up word in database 2266 $Word = $this->DB->Query(
"SELECT WordText" 2267 .
" FROM ".$TableName
2268 .
" WHERE WordId='".$WordId.
"'",
2271 # save word to cache 2272 $WordCache[$WordId] = $Word;
2275 # return word to caller 2284 private function GetItemType($ItemId)
2286 static $ItemTypeCache;
2287 if (!isset($ItemTypeCache))
2289 $this->DB->Query(
"SELECT * FROM SearchItemTypes");
2290 $ItemTypeCache = $this->DB->FetchColumn(
"ItemType",
"ItemId");
2292 return isset($ItemTypeCache[$ItemId])
2293 ? (int)$ItemTypeCache[$ItemId] : NULL;
2301 protected function DMsg($Level, $Msg)
2305 print
"SE: ".$Msg.
"<br>\n";
2309 # ---- BACKWARD COMPATIBILITY -------------------------------------------- 2311 # possible types of logical operators 2315 # 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.
static BuildMultiTypeResults($Results)
Expand a one-dimensional array(ItemId => ItemScore) into a two-dimensional array(ItemType => array(It...
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...
static FlattenMultiTypeResults($Results)
Flatten a two-dimensional array keyed by ItemType with results for each type as the outer values into...
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.