4 # FILE: SearchEngine.php
6 # Open Source Metadata Archive Search Engine (OSMASE)
7 # Copyright 2002-2011 Edward Almasy and Internet Scout
8 # http://scout.wisc.edu
13 # ---- PUBLIC INTERFACE --------------------------------------------------
15 # possible types of logical operators
19 # flags used for indicating field types
34 # save database object for our use
37 # save item access parameters
44 # define flags used for indicating word states
45 if (!defined(
"WORD_PRESENT")) { define(
"WORD_PRESENT", 1); }
46 if (!defined(
"WORD_EXCLUDED")) { define(
"WORD_EXCLUDED", 2); }
47 if (!defined(
"WORD_REQUIRED")) { define(
"WORD_REQUIRED", 4); }
49 # set default debug state
53 # add field to be searched
55 $FieldName, $DBFieldName, $FieldType, $Weight, $UsedInKeywordSearch)
58 $this->FieldInfo[$FieldName][
"DBFieldName"] = $DBFieldName;
59 $this->FieldInfo[$FieldName][
"FieldType"] = $FieldType;
60 $this->FieldInfo[$FieldName][
"Weight"] = $Weight;
61 $this->FieldInfo[$FieldName][
"InKeywordSearch"] = $UsedInKeywordSearch;
64 # retrieve info about tables and fields (useful for child objects)
68 {
return $this->FieldInfo[$FieldName][
"DBFieldName"]; }
70 {
return $this->FieldInfo[$FieldName][
"FieldType"]; }
72 {
return $this->FieldInfo[$FieldName][
"Weight"]; }
74 {
return $this->FieldInfo[$FieldName][
"InKeywordSearch"]; }
83 # ---- search functions
85 # perform keyword search
86 function Search($SearchString, $StartingResult = 0, $NumberOfResults = 10,
87 $SortByField = NULL, $SortDescending = TRUE)
89 $SearchString = $this->SetDebugLevel($SearchString);
90 $this->
DMsg(0,
"In Search() with search string \"".$SearchString.
"\"");
92 # save start time to use in calculating search time
93 $StartTime = microtime(TRUE);
96 $this->InclusiveTermCount = 0;
97 $this->RequiredTermCount = 0;
98 $this->ExcludedTermCount = 0;
100 # parse search string into terms
101 $Words = $this->ParseSearchStringForWords($SearchString);
102 $this->
DMsg(1,
"Found ".count($Words).
" words");
104 # parse search string for phrases
105 $Phrases = $this->ParseSearchStringForPhrases($SearchString);
106 $this->
DMsg(1,
"Found ".count($Phrases).
" phrases");
108 # if only excluded terms specified
109 if ($this->ExcludedTermCount && !$this->InclusiveTermCount)
112 $this->
DMsg(1,
"Loading all records");
113 $Scores = $this->LoadScoresForAllRecords();
118 $Scores = $this->SearchForWords($Words);
119 $this->
DMsg(1,
"Found ".count($Scores).
" results after word search");
120 $Scores = $this->SearchForPhrases($Phrases, $Scores);
121 $this->
DMsg(1,
"Found ".count($Scores).
" results after phrase search");
124 # if search results found
125 if (count($Scores) > 0)
127 # handle any excluded words
128 $Scores = $this->FilterOnExcludedWords($Words, $Scores);
130 # strip off any results that don't contain required words
131 $Scores = $this->FilterOnRequiredWords($Scores);
134 # count, sort, and trim search result scores list
135 $Scores = $this->CleanScores($Scores, $StartingResult, $NumberOfResults,
136 $SortByField, $SortDescending);
139 $this->LastSearchTime = microtime(TRUE) - $StartTime;
141 # return list of items to caller
142 $this->
DMsg(0,
"Ended up with ".$this->NumberOfResultsAvailable.
" results");
146 # perform search across multiple fields and return trimmed results to caller
147 function FieldedSearch($SearchStrings, $StartingResult = 0, $NumberOfResults = 10,
148 $SortByField = NULL, $SortDescending = TRUE)
150 $SearchStrings = $this->SetDebugLevel($SearchStrings);
151 $this->
DMsg(0,
"In FieldedSearch() with "
152 .count($SearchStrings).
" search strings");
154 # save start time to use in calculating search time
155 $StartTime = microtime(TRUE);
158 $Scores = $this->SearchAcrossFields($SearchStrings);
159 $Scores = ($Scores === NULL) ? array() : $Scores;
161 # count, sort, and trim search result scores list
162 $Scores = $this->CleanScores($Scores, $StartingResult, $NumberOfResults,
163 $SortByField, $SortDescending);
166 $this->LastSearchTime = microtime(TRUE) - $StartTime;
168 # return list of items to caller
169 $this->
DMsg(0,
"Ended up with ".$this->NumberOfResultsAvailable.
" results");
173 # perform search with logical groups of fielded searches
174 function GroupedSearch($SearchGroups, $StartingResult = 0, $NumberOfResults = 10,
175 $SortByField = NULL, $SortDescending = TRUE)
177 foreach ($SearchGroups as $Index => $Groups)
179 if (isset($SearchGroups[$Index][
"SearchStrings"]))
181 $SearchGroups[$Index][
"SearchStrings"] =
182 $this->SetDebugLevel($SearchGroups[$Index][
"SearchStrings"]);
185 $this->
DMsg(0,
"In GroupedSearch() with "
186 .count($SearchGroups).
" search groups");
188 # save start time to use in calculating search time
189 $StartTime = microtime(TRUE);
191 # start with no results
194 # save AND/OR search setting
197 # for each search group
199 foreach ($SearchGroups as $Group)
201 $this->
DMsg(0,
"----- GROUP ---------------------------");
203 # if group has AND/OR setting specified
204 if (isset($Group[
"Logic"]))
206 # use specified AND/OR setting
211 # use saved AND/OR setting
214 $this->
DMsg(2,
"Logic is "
217 # if we have search strings for this group
218 if (isset($Group[
"SearchStrings"]))
221 $GroupScores = $this->SearchAcrossFields($Group[
"SearchStrings"]);
223 # if search was conducted
224 if ($GroupScores !== NULL)
226 # if saved AND/OR setting is OR or this is first search
227 if (($SavedSearchLogic == self::LOGIC_OR) || $FirstSearch)
229 # add search results to result list
230 foreach ($GroupScores as $ItemId => $Score)
232 if (isset($Scores[$ItemId]))
234 $Scores[$ItemId] += $Score;
238 $Scores[$ItemId] = $Score;
242 # (reset flag indicating first search)
243 $FirstSearch = FALSE;
247 # AND search results with previous results
248 $OldScores = $Scores;
250 foreach ($GroupScores as $ItemId => $Score)
252 if (isset($OldScores[$ItemId]))
254 $Scores[$ItemId] = $OldScores[$ItemId] + $Score;
262 # restore AND/OR search setting
265 # count, sort, and trim search result scores list
266 $Scores = $this->CleanScores($Scores, $StartingResult, $NumberOfResults,
267 $SortByField, $SortDescending);
270 $this->LastSearchTime = microtime(TRUE) - $StartTime;
272 # return search results to caller
273 $this->
DMsg(0,
"Ended up with ".$this->NumberOfResultsAvailable.
" results");
277 # add function that will be called to filter search results
280 # save filter function name
281 $this->FilterFuncs[] = $FunctionName;
284 # get or set default search logic (AND or OR)
287 if ($NewSetting != NULL)
313 return $this->SearchTermList;
321 # report total weight for all fields involved in search
325 $IncludedKeywordSearch = FALSE;
326 foreach ($SearchStrings as $FieldName => $SearchStringArray)
328 if ($FieldName ==
"XXXKeywordXXX")
330 $IncludedKeywordSearch = TRUE;
334 if (array_key_exists($FieldName, $this->FieldInfo))
336 $Weight += $this->FieldInfo[$FieldName][
"Weight"];
340 if ($IncludedKeywordSearch)
342 foreach ($this->FieldInfo as $FieldName => $Info)
344 if ($Info[
"InKeywordSearch"])
346 $Weight += $Info[
"Weight"];
354 # ---- search database update functions
356 # update search DB for the specified item
359 # bail out if item ID is negative (indicating a temporary record)
360 if ($ItemId < 0) {
return; }
362 # clear word count added flags for this item
363 unset($this->WordCountAdded);
365 # delete any existing info for this item
366 $this->DB->Query(
"DELETE FROM SearchWordCounts WHERE ItemId = ".$ItemId);
368 # for each metadata field
369 foreach ($this->FieldInfo as $FieldName => $Info)
371 # if search weight for field is positive
372 if ($Info[
"Weight"] > 0)
374 # retrieve text for field
380 # for each text string in array
381 foreach ($Text as $String)
383 # record search info for text
384 $this->RecordSearchInfoForText($ItemId, $FieldName,
385 $Info[
"Weight"], $String,
386 $Info[
"InKeywordSearch"]);
391 # record search info for text
392 $this->RecordSearchInfoForText($ItemId, $FieldName,
393 $Info[
"Weight"], $Text,
394 $Info[
"InKeywordSearch"]);
400 # update search DB for the specified range of items
403 # retrieve IDs for specified number of items starting at specified ID
404 $this->DB->Query(
"SELECT ".$this->
ItemIdFieldName.
" FROM ".$this->ItemTableName
405 .
" WHERE ".$this->ItemIdFieldName.
" >= ".$StartingItemId
406 .
" ORDER BY ".$this->ItemIdFieldName.
" LIMIT ".$NumberOfItems);
409 # for each retrieved item ID
410 foreach ($ItemIds as $ItemId)
412 # update search info for item
416 # return ID of last item updated to caller
420 # drop all data pertaining to item from search DB
423 # drop all entries pertaining to item from word count table
424 $this->DB->Query(
"DELETE FROM SearchWordCounts WHERE ItemId = ".$ItemId);
427 # drop all data pertaining to field from search DB
430 # retrieve our ID for field
431 $FieldId = $this->DB->Query(
"SELECT FieldId FROM SearchFields "
432 .
"WHERE FieldName = '".addslashes($FieldName).
"'",
"FieldId");
434 # drop all entries pertaining to field from word counts table
435 $this->DB->Query(
"DELETE FROM SearchWordCounts WHERE FieldId = \'".$FieldId.
"\'");
437 # drop field from our fields table
438 $this->DB->Query(
"DELETE FROM SearchFields WHERE FieldId = \'".$FieldId.
"\'");
441 # return total number of terms indexed by search engine
444 return $this->DB->Query(
"SELECT COUNT(*) AS TermCount"
445 .
" FROM SearchWords",
"TermCount");
448 # return total number of items indexed by search engine
451 return $this->DB->Query(
"SELECT COUNT(DISTINCT ItemId) AS ItemCount"
452 .
" FROM SearchWordCounts",
"ItemCount");
463 # asssume no synonyms will be added
467 $WordId = $this->GetWordId($Word, TRUE);
469 # for each synonym passed in
470 foreach ($Synonyms as $Synonym)
473 $SynonymId = $this->GetWordId($Synonym, TRUE);
475 # if synonym is not already in database
476 $this->DB->Query(
"SELECT * FROM SearchWordSynonyms"
477 .
" WHERE (WordIdA = ".$WordId
478 .
" AND WordIdB = ".$SynonymId.
")"
479 .
" OR (WordIdB = ".$WordId
480 .
" AND WordIdA = ".$SynonymId.
")");
481 if ($this->DB->NumRowsSelected() == 0)
483 # add synonym entry to database
484 $this->DB->Query(
"INSERT INTO SearchWordSynonyms"
485 .
" (WordIdA, WordIdB)"
486 .
" VALUES (".$WordId.
", ".$SynonymId.
")");
491 # report to caller number of new synonyms added
499 $WordId = $this->GetWordId($Word);
502 if ($WordId !== NULL)
504 # if no specific synonyms provided
505 if ($Synonyms === NULL)
507 # remove all synonyms for word
508 $this->DB->Query(
"DELETE FROM SearchWordSynonyms"
509 .
" WHERE WordIdA = '".$WordId.
"'"
510 .
" OR WordIdB = '".$WordId.
"'");
514 # for each specified synonym
515 foreach ($Synonyms as $Synonym)
517 # look up ID for synonym
518 $SynonymId = $this->GetWordId($Synonym);
520 # if synonym ID was found
521 if ($SynonymId !== NULL)
523 # delete synonym entry
524 $this->DB->Query(
"DELETE FROM SearchWordSynonyms"
525 .
" WHERE (WordIdA = '".$WordId.
"'"
526 .
" AND WordIdB = '".$SynonymId.
"')"
527 .
" OR (WordIdB = '".$WordId.
"'"
528 .
" AND WordIdA = '".$SynonymId.
"')");
535 # remove all synonyms
538 $this->DB->Query(
"DELETE FROM SearchWordSynonyms");
541 # get synonyms for word (returns array of synonyms)
544 # assume no synonyms will be found
547 # look up ID for word
548 $WordId = $this->GetWordId($Word);
550 # if word ID was found
551 if ($WordId !== NULL)
553 # look up IDs of all synonyms for this word
554 $this->DB->Query(
"SELECT WordIdA, WordIdB FROM SearchWordSynonyms"
555 .
" WHERE WordIdA = ".$WordId
556 .
" OR WordIdB = ".$WordId);
557 $SynonymIds = array();
558 while ($Record = $this->DB->FetchRow)
560 $SynonymIds[] = ($Record[
"WordIdA"] == $WordId)
561 ? $Record[
"WordIdB"] : $Record[
"WordIdA"];
564 # for each synonym ID
565 foreach ($SynonymIds as $SynonymId)
567 # look up synonym word and add to synonym list
568 $Synonyms[] = $this->GetWord($SynonymId);
572 # return synonyms to caller
576 # get all synonyms (returns 2D array w/ words as first index)
579 # assume no synonyms will be found
580 $SynonymList = array();
582 # for each synonym ID pair
584 $OurDB->Query(
"SELECT WordIdA, WordIdB FROM SearchWordSynonyms");
585 while ($Record = $OurDB->FetchRow())
588 $Word = $this->GetWord($Record[
"WordIdA"]);
589 $Synonym = $this->GetWord($Record[
"WordIdB"]);
591 # if we do not already have an entry for the word
592 # or synonym is not listed for this word
593 if (!isset($SynonymList[$Word])
594 || !in_array($Synonym, $SynonymList[$Word]))
596 # add entry for synonym
597 $SynonymList[$Word][] = $Synonym;
600 # if we do not already have an entry for the synonym
601 # or word is not listed for this synonym
602 if (!isset($SynonymList[$Synonym])
603 || !in_array($Word, $SynonymList[$Synonym]))
606 $SynonymList[$Synonym][] = $Word;
611 # (this loop removes reciprocal duplicates)
612 foreach ($SynonymList as $Word => $Synonyms)
614 # for each synonym for that word
615 foreach ($Synonyms as $Synonym)
617 # if synonym has synonyms and word is one of them
618 if (isset($SynonymList[$Synonym])
619 && isset($SynonymList[$Word])
620 && in_array($Word, $SynonymList[$Synonym])
621 && in_array($Synonym, $SynonymList[$Word]))
623 # if word has less synonyms than synonym
624 if (count($SynonymList[$Word])
625 < count($SynonymList[$Synonym]))
627 # remove synonym from synonym list for word
628 $SynonymList[$Word] = array_diff(
629 $SynonymList[$Word], array($Synonym));
631 # if no synonyms left for word
632 if (!count($SynonymList[$Word]))
634 # remove empty synonym list for word
635 unset($SynonymList[$Word]);
640 # remove word from synonym list for synonym
641 $SynonymList[$Synonym] = array_diff(
642 $SynonymList[$Synonym], array($Word));
644 # if no synonyms left for word
645 if (!count($SynonymList[$Synonym]))
647 # remove empty synonym list for word
648 unset($SynonymList[$Synonym]);
655 # sort array alphabetically (just for convenience)
656 foreach ($SynonymList as $Word => $Synonyms)
658 asort($SynonymList[$Word]);
662 # return 2D array of synonyms to caller
666 # set all synonyms (accepts 2D array w/ words as first index)
669 # remove all existing synonyms
672 # for each synonym entry passed in
673 foreach ($SynonymList as $Word => $Synonyms)
675 # add synonyms for word
690 # asssume no synonyms will be added
693 # read in contents of file
694 $Lines = file($FileName, FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES);
696 # if file contained lines
699 # for each line of file
700 foreach ($Lines as $Line)
702 # if line is not a comment
703 if (!preg_match(
"/[\s]*#/", $Line))
705 # split line into words
706 $Words = preg_split(
"/[\s,]+/", $Line);
709 if (count($Words) > 1)
711 # separate out word and synonyms
712 $Word = array_shift($Words);
721 # return count of synonyms added to caller
725 # suggest alternatives
732 # ---- PRIVATE INTERFACE -------------------------------------------------
748 private $WordCountAdded;
751 private $RequiredTermCount;
752 private $RequiredTermCounts;
753 private $InclusiveTermCount;
754 private $ExcludedTermCount;
755 private $SearchTermList;
760 # ---- common private functions (used in both searching and DB build)
762 # normalize and parse search string into list of search terms
763 private function ParseSearchStringForWords($SearchString, $IgnorePhrases = FALSE)
765 # strip off any surrounding whitespace
766 $Text = trim($SearchString);
768 # set up normalization replacement strings
770 "/'s[^a-z0-9\\-+~]+/i", #
get rid of possessive plurals
771 "/'/", #
get rid of single quotes / apostrophes
772 "/\"[^\"]*\"/", #
get rid of phrases (NOTE: HARD-CODED INDEX BELOW!!!)
"
773 "/\\([^)]*\\)/
", # get rid of groups (NOTE: HARD-CODED INDEX BELOW!!!)
774 "/[^a-z0-9\\-+~]+/i
", # convert non-alphanumerics / non-minus/plus to a space
775 "/([^\\s])-+/i
", # convert minus preceded by anything but whitespace to a space
776 "/([^\\s])\\++/i
", # convert plus preceded by anything but whitespace to a space
777 "/-\\s/i
", # convert minus followed by whitespace to a space
778 "/\\+\\s/i
", # convert plus followed by whitespace to a space
779 "/~\\s/i
", # convert tilde followed by whitespace to a space
780 "/[ ]+/
" # convert multiple spaces to one space
782 $Replacements = array(
796 # if we are supposed to ignore phrases and groups (series of words in quotes or surrounded by parens)
799 # switch phrase removal to double quote removal (HARD-CODED INDEX INTO PATTERN LIST!!)
800 $Patterns[2] = "/\
"/";
802 # switch group removal to paren removal (HARD-CODED INDEX INTO PATTERN LIST!!)
803 $Patterns[3] =
"/[\(\)]+/";
806 # remove punctuation from text and normalize whitespace
807 $Text = preg_replace($Patterns, $Replacements, $Text);
808 $this->
DMsg(2,
"Normalized search string is '".$Text.
"'");
810 # convert text to lower case
811 $Text = strtolower($Text);
813 # strip off any extraneous whitespace
816 # start with an empty array
819 # if we have no words left after parsing
820 if (strlen($Text) != 0)
823 foreach (explode(
" ", $Text) as $Word)
825 # grab first character of word
826 $FirstChar = substr($Word, 0, 1);
828 # strip off option characters and set flags appropriately
829 $Flags = WORD_PRESENT;
830 if ($FirstChar ==
"-")
832 $Word = substr($Word, 1);
833 $Flags |= WORD_EXCLUDED;
834 if (!isset($Words[$Word]))
836 $this->ExcludedTermCount++;
841 if ($FirstChar ==
"~")
843 $Word = substr($Word, 1);
846 || ($FirstChar ==
"+"))
848 if ($FirstChar ==
"+")
850 $Word = substr($Word, 1);
852 $Flags |= WORD_REQUIRED;
853 if (!isset($Words[$Word]))
855 $this->RequiredTermCount++;
858 if (!isset($Words[$Word]))
860 $this->InclusiveTermCount++;
861 $this->SearchTermList[] = $Word;
865 # store flags to indicate word found
866 $Words[$Word] = $Flags;
867 $this->
DMsg(3,
"Word identified (".$Word.
")");
871 # return normalized words to caller
877 # if field ID is not in cache
878 if (!isset($this->FieldIds[$FieldName]))
880 # look up field info in database
881 $this->DB->Query(
"SELECT FieldId FROM SearchFields "
882 .
"WHERE FieldName = '".addslashes($FieldName).
"'");
885 if ($Record = $this->DB->FetchRow())
887 # load info from DB record
888 $FieldId = $Record[
"FieldId"];
892 # add field to database
893 $this->DB->Query(
"INSERT INTO SearchFields (FieldName) "
894 .
"VALUES ('".addslashes($FieldName).
"')");
896 # retrieve ID for newly added field
897 $FieldId = $this->DB->LastInsertId();
901 $this->FieldIds[$FieldName] = $FieldId;
904 # return cached ID to caller
905 return $this->FieldIds[$FieldName];
908 # retrieve ID for specified word (returns NULL if no ID found)
909 private function GetWordId($Word, $AddIfNotFound = FALSE)
913 # if word was in ID cache
914 if (isset($WordIdCache[$Word]))
917 $WordId = $WordIdCache[$Word];
921 # look up ID in database
922 $WordId = $this->DB->Query(
"SELECT WordId"
924 .
" WHERE WordText='".addslashes($Word).
"'",
927 # if ID was not found and caller requested it be added
928 if (($WordId === NULL) && $AddIfNotFound)
930 # add word to database
931 $this->DB->Query(
"INSERT INTO SearchWords (WordText)"
932 .
" VALUES ('".addslashes(strtolower($Word)).
"')");
934 # get ID for newly added word
935 $WordId = $this->DB->LastInsertId();
939 $WordIdCache[$Word] = $WordId;
942 # return ID to caller
946 # retrieve ID for specified word stem (returns NULL if no ID found)
947 private function GetStemId($Stem, $AddIfNotFound = FALSE)
951 # if stem was in ID cache
952 if (isset($StemIdCache[$Stem]))
955 $StemId = $StemIdCache[$Stem];
959 # look up ID in database
960 $StemId = $this->DB->Query(
"SELECT WordId"
962 .
" WHERE WordText='".addslashes($Stem).
"'",
965 # if ID was not found and caller requested it be added
966 if (($StemId === NULL) && $AddIfNotFound)
968 # add stem to database
969 $this->DB->Query(
"INSERT INTO SearchStems (WordText)"
970 .
" VALUES ('".addslashes(strtolower($Stem)).
"')");
972 # get ID for newly added stem
973 $StemId = $this->DB->LastInsertId();
976 # adjust from DB ID value to stem ID value
977 $StemId += self::STEM_ID_OFFSET;
980 $StemIdCache[$Stem] = $StemId;
983 # return ID to caller
987 # retrieve word for specified word ID (returns FALSE if no word found)
988 private function GetWord($WordId)
992 # if word was in cache
993 if (isset($WordCache[$WordId]))
995 # use word from cache
996 $Word = $WordCache[$WordId];
1000 # adjust search location and word ID if word is stem
1001 $TableName =
"SearchWords";
1002 if ($WordId >= self::STEM_ID_OFFSET)
1004 $TableName =
"SearchStems";
1005 $WordId -= self::STEM_ID_OFFSET;
1008 # look up word in database
1009 $Word = $this->DB->Query(
"SELECT WordText"
1010 .
" FROM ".$TableName
1011 .
" WHERE WordId='".$WordId.
"'",
1014 # save word to cache
1015 $WordCache[$WordId] = $Word;
1018 # return word to caller
1023 # ---- private functions used in searching
1025 # perform search across multiple fields and return raw results to caller
1026 private function SearchAcrossFields($SearchStrings)
1028 # start by assuming no search will be done
1030 $ReferentScores = NULL;
1033 $this->InclusiveTermCount = 0;
1034 $this->RequiredTermCount = 0;
1035 $this->ExcludedTermCount = 0;
1037 # construct a search engine object used to isolate reference-based
1038 # searches from normal search parameters
1041 $this->ItemTableName,
1042 $this->ItemIdFieldName,
1043 $this->ReferenceTableName,
1044 $this->ReferenceSourceIdFieldName,
1045 $this->ReferenceDestinationIdFieldName);
1048 $NeedComparisonSearch = FALSE;
1049 foreach ($SearchStrings as $FieldName => $SearchStringArray)
1051 # convert search string to array if needed
1052 if (!is_array($SearchStringArray))
1054 $SearchStringArray = array($SearchStringArray);
1057 # for each search string for this field
1058 foreach ($SearchStringArray as $SearchString)
1060 # if field is keyword or field is text and does not look like comparison match
1061 $NotComparisonSearch = !preg_match(
"/^[><!]=./", $SearchString)
1062 && !preg_match(
"/^[><=]./", $SearchString);
1063 if (($FieldName ==
"XXXKeywordXXX")
1064 || (isset($this->FieldInfo[$FieldName])
1065 && ($this->FieldInfo[$FieldName][
"FieldType"]
1066 == self::FIELDTYPE_TEXT)
1067 && $NotComparisonSearch))
1069 $this->DMsg(0,
"Searching text field \""
1070 .$FieldName.
"\" for string \"$SearchString\"");
1072 # normalize text and split into words
1073 $Words[$FieldName] =
1074 $this->ParseSearchStringForWords($SearchString);
1076 # calculate scores for matching items
1077 if (count($Words[$FieldName]))
1079 $Scores = $this->SearchForWords(
1080 $Words[$FieldName], $FieldName, $Scores);
1081 $this->DMsg(3,
"Have "
1082 .count($Scores).
" results after word search");
1085 # split into phrases
1086 $Phrases[$FieldName] =
1087 $this->ParseSearchStringForPhrases($SearchString);
1089 # handle any phrases
1090 if (count($Phrases[$FieldName]))
1092 $Scores = $this->SearchForPhrases(
1093 $Phrases[$FieldName], $Scores, $FieldName, TRUE, FALSE);
1094 $this->DMsg(3,
"Have "
1095 .count($Scores).
" results after phrase search");
1100 # set flag to indicate possible comparison search candidate found
1101 $NeedComparisonSearch = TRUE;
1106 # perform comparison searches
1107 if ($NeedComparisonSearch)
1109 $Scores = $this->SearchForComparisonMatches($SearchStrings, $Scores);
1110 $this->DMsg(3,
"Have ".count($Scores).
" results after comparison search");
1113 # if reference search results were found
1114 if (count($ReferentScores))
1116 # where merged scores (reference ANDed with normal scores) are
1117 # stored for this block
1118 $MergedScores = array();
1120 # get reference items and scores from the referent item
1121 foreach ($ReferentScores as $ItemId => $Score)
1123 # query for source references for the referent
1125 SELECT * FROM ".$this->ReferenceTableName.
"
1126 WHERE ".$this->ReferenceDestinationIdFieldName.
" = '".addslashes($ItemId).
"'");
1128 # loop through each found item
1129 while (FALSE !== ($Row = $this->DB->FetchRow()))
1131 $ReferenceId = $Row[$this->ReferenceSourceIdFieldName];
1133 # add to an existing score
1134 if (isset($Scores[$ReferenceId]))
1136 $MergedScores[$ReferenceId] =
1137 $Scores[$ReferenceId] + $Score;
1140 # create a score for the reference item
1143 $MergedScores[$ReferenceId] = $Score;
1148 # replace the existing scores with the merged ones
1149 $Scores = $MergedScores;
1152 # if no results found and exclusions specified
1153 if (!count($Scores) && $this->ExcludedTermCount)
1156 $Scores = $this->LoadScoresForAllRecords();
1159 # if search results found
1162 # for each search text string
1163 foreach ($SearchStrings as $FieldName => $SearchStringArray)
1165 # convert search string to array if needed
1166 if (!is_array($SearchStringArray))
1168 $SearchStringArray = array($SearchStringArray);
1171 # for each search string for this field
1172 foreach ($SearchStringArray as $SearchString)
1175 if (($FieldName ==
"XXXKeywordXXX")
1176 || (isset($this->FieldInfo[$FieldName])
1177 && ($this->FieldInfo[$FieldName][
"FieldType"]
1178 == self::FIELDTYPE_TEXT)))
1180 # if there are words in search text
1181 if (isset($Words[$FieldName]))
1183 # handle any excluded words
1184 $Scores = $this->FilterOnExcludedWords($Words[$FieldName], $Scores, $FieldName);
1187 # handle any excluded phrases
1188 if (isset($Phrases[$FieldName]))
1190 $Scores = $this->SearchForPhrases(
1191 $Phrases[$FieldName], $Scores, $FieldName, FALSE, TRUE);
1197 # strip off any results that don't contain required words
1198 $Scores = $this->FilterOnRequiredWords($Scores);
1201 # return search result scores to caller
1205 # search for words in specified field
1206 private function SearchForWords(
1207 $Words, $FieldName =
"XXXKeywordXXX", $Scores = NULL)
1211 # start with empty search result scores list if none passed in
1212 if ($Scores == NULL)
1218 $FieldId = $this->GetFieldId($FieldName);
1221 foreach ($Words as $Word => $Flags)
1224 $this->DMsg(2,
"Searching for word '${Word}' in field ".$FieldName);
1226 # if word is not excluded
1227 if (!($Flags & WORD_EXCLUDED))
1229 # look up record ID for word
1230 $this->DMsg(2,
"Looking up word \"".$Word.
"\"");
1231 $WordId = $this->GetWordId($Word);
1234 if ($WordId !== NULL)
1236 # look up counts for word
1237 $DB->Query(
"SELECT ItemId,Count FROM SearchWordCounts "
1238 .
"WHERE WordId = ".$WordId
1239 .
" AND FieldId = ".$FieldId);
1240 $Counts = $DB->FetchColumn(
"Count",
"ItemId");
1242 # if synonym support is enabled
1243 if ($this->SynonymsEnabled)
1245 # look for any synonyms
1246 $DB->Query(
"SELECT WordIdA, WordIdB"
1247 .
" FROM SearchWordSynonyms"
1248 .
" WHERE WordIdA = ".$WordId
1249 .
" OR WordIdB = ".$WordId);
1251 # if synonyms were found
1252 if ($DB->NumRowsSelected())
1254 # retrieve synonym IDs
1255 $SynonymIds = array();
1256 while ($Record = $DB->FetchRow())
1258 $SynonymIds[] = ($Record[
"WordIdA"] == $WordId)
1259 ? $Record[
"WordIdB"]
1260 : $Record[
"WordIdA"];
1264 foreach ($SynonymIds as $SynonymId)
1266 # retrieve counts for synonym
1267 $DB->Query(
"SELECT ItemId,Count"
1268 .
" FROM SearchWordCounts"
1269 .
" WHERE WordId = ".$SynonymId
1270 .
" AND FieldId = ".$FieldId);
1271 $SynonymCounts = $DB->FetchColumn(
"Count",
"ItemId");
1274 foreach ($SynonymCounts as $ItemId => $Count)
1276 # adjust count because it's a synonym
1277 $AdjustedCount = ceil($Count / 2);
1279 # add count to existing counts
1280 if (isset($Counts[$ItemId]))
1282 $Counts[$ItemId] += $AdjustedCount;
1286 $Counts[$ItemId] = $AdjustedCount;
1294 # if stemming is enabled
1295 if ($this->StemmingEnabled)
1298 $Stem = PorterStemmer::Stem($Word);
1299 $this->DMsg(2,
"Looking up stem \"".$Stem.
"\"");
1300 $StemId = $this->GetStemId($Stem);
1302 # if ID found for stem
1303 if ($StemId !== NULL)
1305 # retrieve counts for stem
1306 $DB->Query(
"SELECT ItemId,Count"
1307 .
" FROM SearchWordCounts"
1308 .
" WHERE WordId = ".$StemId
1309 .
" AND FieldId = ".$FieldId);
1310 $StemCounts = $DB->FetchColumn(
"Count",
"ItemId");
1313 foreach ($StemCounts as $ItemId => $Count)
1315 # adjust count because it's a stem
1316 $AdjustedCount = ceil($Count / 2);
1318 # add count to existing counts
1319 if (isset($Counts[$ItemId]))
1321 $Counts[$ItemId] += $AdjustedCount;
1325 $Counts[$ItemId] = $AdjustedCount;
1331 # if counts were found
1335 foreach ($Counts as $ItemId => $Count)
1337 # if word flagged as required
1338 if ($Flags & WORD_REQUIRED)
1340 # increment required word count for record
1341 if (isset($this->RequiredTermCounts[$ItemId]))
1343 $this->RequiredTermCounts[$ItemId]++;
1347 $this->RequiredTermCounts[$ItemId] = 1;
1351 # add to item record score
1352 if (isset($Scores[$ItemId]))
1354 $Scores[$ItemId] += $Count;
1358 $Scores[$ItemId] = $Count;
1365 # return basic scores to caller
1369 # extract phrases (terms surrounded by quotes) from search string
1370 private function ParseSearchStringForPhrases($SearchString)
1372 # split into chunks delimited by double quote marks
1373 $Pieces = explode(
"\"", $SearchString); #
"
1375 # for each pair of chunks
1378 while ($Index < count($Pieces))
1380 # grab phrase from chunk
1381 $Phrase = trim(addslashes($Pieces[$Index - 1]));
1382 $Flags = WORD_PRESENT;
1384 # grab first character of phrase
1385 $FirstChar = substr($Pieces[$Index - 2], -1);
1387 # set flags to reflect any option characters
1388 if ($FirstChar == "-
")
1390 $Flags |= WORD_EXCLUDED;
1391 if (!isset($Phrases[$Phrase]))
1393 $this->ExcludedTermCount++;
1398 if ((($this->DefaultSearchLogic == self::LOGIC_AND) && ($FirstChar != "~
"))
1399 || ($FirstChar == "+
"))
1401 $Flags |= WORD_REQUIRED;
1402 if (!isset($Phrases[$Phrase]))
1404 $this->RequiredTermCount++;
1407 if (!isset($Phrases[$Phrase]))
1409 $this->InclusiveTermCount++;
1410 $this->SearchTermList[] = $Phrase;
1413 $Phrases[$Phrase] = $Flags;
1415 # move to next pair of chunks
1419 # return phrases to caller
1423 # extract groups (terms surrounded by parens) from search string
1424 # (NOTE: NOT YET IMPLEMENTED!!!)
1425 private function ParseSearchStringForGroups($SearchString)
1427 # split into chunks delimited by open paren
1428 $Pieces = explode("(
", $SearchString);
1432 while ($Index < count($Pieces))
1434 # grab phrase from chunk
1435 $Group = trim(addslashes($Pieces[$Index - 1]));
1438 # move to next pair of chunks
1442 # return phrases to caller
1446 protected function SearchFieldForPhrases($FieldName, $Phrase)
1449 exit("<br>SE - ERROR: SearchFieldForPhrases() not implemented<br>\n");
1452 private function SearchForPhrases($Phrases, $Scores, $FieldName = "XXXKeywordXXX",
1453 $ProcessNonExcluded = TRUE, $ProcessExcluded = TRUE)
1455 # if phrases are found
1456 if (count($Phrases) > 0)
1458 # if this is a keyword search
1459 if ($FieldName ==
"XXXKeywordXXX")
1462 foreach ($this->FieldInfo as $KFieldName => $Info)
1464 # if field is marked to be included in keyword searches
1465 if ($Info[
"InKeywordSearch"])
1467 # call ourself with that field
1468 $Scores = $this->SearchForPhrases($Phrases, $Scores, $KFieldName,
1469 $ProcessNonExcluded, $ProcessExcluded);
1476 foreach ($Phrases as $Phrase => $Flags)
1478 $this->DMsg(2,
"Searching for phrase '".$Phrase
1479 .
"' in field ".$FieldName);
1481 # if phrase flagged as excluded and we are doing excluded phrases
1482 # or phrase flagged as non-excluded and we are doing non-excluded phrases
1483 if (($ProcessExcluded && ($Flags & WORD_EXCLUDED))
1484 || ($ProcessNonExcluded && !($Flags & WORD_EXCLUDED)))
1486 # initialize score list if necessary
1487 if ($Scores === NULL) { $Scores = array(); }
1489 # retrieve list of items that contain phrase
1490 $ItemIds = $this->SearchFieldForPhrases(
1491 $FieldName, $Phrase);
1493 # for each item that contains phrase
1494 foreach ($ItemIds as $ItemId)
1496 # if we are doing excluded phrases and phrase flagged as excluded
1497 if ($ProcessExcluded && ($Flags & WORD_EXCLUDED))
1499 # knock item off of list
1500 unset($Scores[$ItemId]);
1502 elseif ($ProcessNonExcluded)
1504 # calculate phrase value based on number of words and field weight
1505 $PhraseScore = count(preg_split(
"/[\s]+/", $Phrase, -1, PREG_SPLIT_NO_EMPTY))
1506 * $this->FieldInfo[$FieldName][
"Weight"];
1507 $this->DMsg(2,
"Phrase score is ".$PhraseScore);
1509 # bump up item record score
1510 if (isset($Scores[$ItemId]))
1512 $Scores[$ItemId] += $PhraseScore;
1516 $Scores[$ItemId] = $PhraseScore;
1519 # if phrase flagged as required
1520 if ($Flags & WORD_REQUIRED)
1522 # increment required word count for record
1523 if (isset($this->RequiredTermCounts[$ItemId]))
1525 $this->RequiredTermCounts[$ItemId]++;
1529 $this->RequiredTermCounts[$ItemId] = 1;
1539 # return updated scores to caller
1543 private function FilterOnExcludedWords($Words, $Scores, $FieldName =
"XXXKeywordXXX")
1548 $FieldId = $this->GetFieldId($FieldName);
1551 foreach ($Words as $Word => $Flags)
1553 # if word flagged as excluded
1554 if ($Flags & WORD_EXCLUDED)
1556 # look up record ID for word
1557 $WordId = $this->GetWordId($Word);
1560 if ($WordId !== NULL)
1562 # look up counts for word
1563 $DB->Query(
"SELECT ItemId FROM SearchWordCounts "
1564 .
"WHERE WordId=${WordId} AND FieldId=${FieldId}");
1567 while ($Record = $DB->FetchRow())
1569 # if item record is in score list
1570 $ItemId = $Record[
"ItemId"];
1571 if (isset($Scores[$ItemId]))
1573 # remove item record from score list
1574 $this->DMsg(3,
"Filtering out item ".$ItemId
1575 .
" because it contained word \"".$Word.
"\"");
1576 unset($Scores[$ItemId]);
1583 # returned filtered score list to caller
1587 private function FilterOnRequiredWords($Scores)
1589 # if there were required words
1590 if ($this->RequiredTermCount > 0)
1593 foreach ($Scores as $ItemId => $Score)
1595 # if item does not meet required word count
1596 if (!isset($this->RequiredTermCounts[$ItemId])
1597 || ($this->RequiredTermCounts[$ItemId] < $this->RequiredTermCount))
1600 $this->DMsg(4,
"Filtering out item ".$ItemId
1601 .
" because it didn't have required word count of "
1602 .$this->RequiredTermCount
1603 .(isset($this->RequiredTermCounts[$ItemId])
1605 .$this->RequiredTermCounts[$ItemId]
1608 unset($Scores[$ItemId]);
1613 # return filtered list to caller
1617 # count, sort, and trim search result scores list
1618 private function CleanScores($Scores, $StartingResult, $NumberOfResults,
1619 $SortByField, $SortDescending)
1621 # perform any requested filtering
1622 $this->DMsg(0,
"Have ".count($Scores).
" results before filter callbacks");
1623 $Scores = $this->FilterOnSuppliedFunctions($Scores);
1625 # save total number of results available
1626 $this->NumberOfResultsAvailable = count($Scores);
1628 # if no sorting field specified
1629 if ($SortByField === NULL)
1631 # sort result list by score
1632 if ($SortDescending)
1633 arsort($Scores, SORT_NUMERIC);
1635 asort($Scores, SORT_NUMERIC);
1639 # get list of item IDs in sorted order
1640 $SortedIds = $this->GetItemIdsSortedByField(
1641 $SortByField, $SortDescending);
1643 # if we have sorted item IDs
1644 if (count($SortedIds) && count($Scores))
1646 # strip sorted ID list down to those that appear in search results
1647 $SortedIds = array_intersect($SortedIds, array_keys($Scores));
1649 # rebuild score list in sorted order
1650 foreach ($SortedIds as $Id)
1652 $NewScores[$Id] = $Scores[$Id];
1654 $Scores = $NewScores;
1658 # sort result list by score
1659 arsort($Scores, SORT_NUMERIC);
1663 # trim result list to match range requested by caller
1664 $ScoresKeys = array_slice(
1665 array_keys($Scores), $StartingResult, $NumberOfResults);
1666 $TrimmedScores = array();
1667 foreach ($ScoresKeys as $Key) { $TrimmedScores[$Key] = $Scores[$Key]; }
1669 # returned cleaned search result scores list to caller
1670 return $TrimmedScores;
1675 # if filter functions have been set
1676 if (isset($this->FilterFuncs))
1679 foreach ($Scores as $ItemId => $Score)
1681 # for each filter function
1682 foreach ($this->FilterFuncs as $FuncName)
1684 # if filter function return TRUE for item
1685 if (call_user_func($FuncName, $ItemId))
1688 $this->DMsg(2,
"Filter callback <i>".$FuncName
1689 .
"</i> rejected item ".$ItemId);
1690 unset($Scores[$ItemId]);
1692 # bail out of filter func loop
1699 # return filtered list to caller
1703 private function SearchForComparisonMatches($SearchStrings, $Scores)
1707 foreach ($SearchStrings as $SearchFieldName => $SearchStringArray)
1709 # if field is not keyword
1710 if ($SearchFieldName !=
"XXXKeywordXXX")
1712 # convert search string to array if needed
1713 if (!is_array($SearchStringArray))
1715 $SearchStringArray = array($SearchStringArray);
1718 # for each search string for this field
1719 foreach ($SearchStringArray as $SearchString)
1721 # if search string looks like comparison search
1722 $FoundOperator = preg_match(
"/^[><!]=./", $SearchString)
1723 || preg_match(
"/^[><=]./", $SearchString);
1725 || (isset($this->FieldInfo[$SearchFieldName][
"FieldType"])
1726 && ($this->FieldInfo[$SearchFieldName][
"FieldType"]
1727 != self::FIELDTYPE_TEXT)))
1730 $Patterns = array(
"/^[><!]=/",
"/^[><=]/");
1731 $Replacements = array(
"",
"");
1732 $Value = trim(preg_replace($Patterns, $Replacements, $SearchString));
1734 # determine and save operator
1735 if (!$FoundOperator)
1737 $Operators[$Index] =
"=";
1741 $Term = trim($SearchString);
1742 $FirstChar = $Term{0};
1743 $FirstTwoChars = $FirstChar.$Term{1};
1744 if ($FirstTwoChars ==
">=") { $Operators[$Index] =
">="; }
1745 elseif ($FirstTwoChars ==
"<=") { $Operators[$Index] =
"<="; }
1746 elseif ($FirstTwoChars ==
"!=") { $Operators[$Index] =
"!="; }
1747 elseif ($FirstChar ==
">") { $Operators[$Index] =
">"; }
1748 elseif ($FirstChar ==
"<") { $Operators[$Index] =
"<"; }
1749 elseif ($FirstChar ==
"=") { $Operators[$Index] =
"="; }
1752 # if operator was found
1753 if (isset($Operators[$Index]))
1756 $Values[$Index] = $Value;
1759 $FieldNames[$Index] = $SearchFieldName;
1760 $this->DMsg(3,
"Added comparison (field = <i>"
1761 .$FieldNames[$Index].
"</i> op = <i>"
1762 .$Operators[$Index].
"</i> val = <i>"
1763 .$Values[$Index].
"</i>)");
1765 # move to next comparison array entry
1773 # if comparisons found
1774 if (isset($Operators))
1776 # perform comparisons on fields and gather results
1777 $Results = $this->SearchFieldsForComparisonMatches($FieldNames, $Operators, $Values);
1779 # if search logic is set to AND
1780 if ($this->DefaultSearchLogic == self::LOGIC_AND)
1782 # if results were found
1783 if (count($Results))
1785 # if there were no prior results and no terms for keyword search
1786 if ((count($Scores) == 0) && ($this->InclusiveTermCount == 0))
1788 # add all results to scores
1789 foreach ($Results as $ItemId)
1791 $Scores[$ItemId] = 1;
1796 # remove anything from scores that is not part of results
1797 foreach ($Scores as $ItemId => $Score)
1799 if (in_array($ItemId, $Results) == FALSE)
1801 unset($Scores[$ItemId]);
1814 # add result items to scores
1815 if ($Scores === NULL) { $Scores = array(); }
1816 foreach ($Results as $ItemId)
1818 if (isset($Scores[$ItemId]))
1820 $Scores[$ItemId] += 1;
1824 $Scores[$ItemId] = 1;
1830 # return results to caller
1834 private function SetDebugLevel($SearchStrings)
1836 # if search info is an array
1837 if (is_array($SearchStrings))
1839 # for each array element
1840 foreach ($SearchStrings as $FieldName => $SearchStringArray)
1842 # if element is an array
1843 if (is_array($SearchStringArray))
1845 # for each array element
1846 foreach ($SearchStringArray as $Index => $SearchString)
1848 # pull out search string if present
1849 $SearchStrings[$FieldName][$Index] = $this->ExtractDebugLevel($SearchString);
1854 # pull out search string if present
1855 $SearchStrings[$FieldName] = $this->ExtractDebugLevel($SearchStringArray);
1861 # pull out search string if present
1862 $SearchStrings = $this->ExtractDebugLevel($SearchStrings);
1865 # return new search info to caller
1866 return $SearchStrings;
1869 private function ExtractDebugLevel($SearchString)
1871 # if search string contains debug level indicator
1872 if (strstr($SearchString,
"DBUGLVL="))
1874 # remove indicator and set debug level
1875 $Level = preg_replace(
"/^\\s*DBUGLVL=([1-9]{1,2}).*/",
"\\1", $SearchString);
1878 $this->DebugLevel = $Level;
1879 $this->DMsg(0,
"Setting debug level to ".$Level);
1880 $SearchString = preg_replace(
"/DBUGLVL=${Level}/",
"", $SearchString);
1884 # return (possibly) modified search string to caller
1885 return $SearchString;
1888 # load and return search result scores array containing all possible records
1889 private function LoadScoresForAllRecords()
1891 # start with empty list
1895 $this->DB->Query(
"SELECT ".$this->ItemIdFieldName
1896 .
" FROM ".$this->ItemTableName);
1897 while ($Record = $this->DB->FetchRow())
1899 # set score for item to 1
1900 $Scores[$Record[$this->ItemIdFieldName]] = 1;
1903 # return array with all scores to caller
1908 # ---- private functions used in building search database
1917 private function UpdateWordCount($Word, $ItemId, $FieldId, $Weight = 1)
1919 # retrieve ID for word
1920 $WordIds[] = $this->GetWordId($Word, TRUE);
1922 # if stemming is enabled
1923 if ($this->StemmingEnabled)
1925 # retrieve ID for stem of word
1926 $Stem = PorterStemmer::Stem($Word, TRUE);
1927 $WordIds[] = $this->GetStemId($Stem, TRUE);
1930 # for word and stem of word
1931 foreach ($WordIds as $WordId)
1933 # if word count already added to database
1934 if (isset($this->WordCountAdded[$WordId][$FieldId]))
1937 $this->DB->Query(
"UPDATE SearchWordCounts SET Count=Count+".$Weight
1938 .
" WHERE WordId=".$WordId
1939 .
" AND ItemId=".$ItemId
1940 .
" AND FieldId=".$FieldId);
1944 # add word count to DB
1945 $this->DB->Query(
"INSERT INTO SearchWordCounts"
1946 .
" (WordId, ItemId, FieldId, Count) VALUES"
1947 .
" (".$WordId.
", ".$ItemId.
", ".$FieldId.
", ".$Weight.
")");
1949 # remember that we added count for this word
1950 $this->WordCountAdded[$WordId][$FieldId] = TRUE;
1953 # decrease weight for stem
1954 $Weight = ceil($Weight / 2);
1961 exit(
"<br>SE - ERROR: GetFieldContent() not implemented<br>\n");
1964 private function RecordSearchInfoForText(
1965 $ItemId, $FieldName, $Weight, $Text, $IncludeInKeyword)
1968 $Words = $this->ParseSearchStringForWords($Text, TRUE);
1970 # if there was text left after parsing
1971 if (count($Words) > 0)
1974 $FieldId = $this->GetFieldId($FieldName);
1976 # if text should be included in keyword searches
1977 if ($IncludeInKeyword)
1979 # get ID for keyword field
1980 $KeywordFieldId = $this->GetFieldId(
"XXXKeywordXXX");
1984 foreach ($Words as $Word => $Flags)
1986 # update count for word
1987 $this->UpdateWordCount($Word, $ItemId, $FieldId);
1989 # if text should be included in keyword searches
1990 if ($IncludeInKeyword)
1992 # update keyword field count for word
1993 $this->UpdateWordCount(
1994 $Word, $ItemId, $KeywordFieldId, $Weight);
2000 # print debug message if level set high enough
2001 protected function DMsg($Level, $Msg)
2003 if ($this->DebugLevel > $Level)
2005 print(
"SE: ".$Msg.
"<br>\n");
2009 # ---- BACKWARD COMPATIBILITY --------------------------------------------
2011 # possible types of logical operators
2012 const SEARCHLOGIC_AND = 1;
2013 const SEARCHLOGIC_OR = 2;
$ReferenceSourceIdFieldName
SetAllSynonyms($SynonymList)
RemoveSynonyms($Word, $Synonyms=NULL)
LoadSynonymsFromFile($FileName)
Load synonyms from a file.
SQL database abstraction object with smart query caching.
Search($SearchString, $StartingResult=0, $NumberOfResults=10, $SortByField=NULL, $SortDescending=TRUE)
$ReferenceDestinationIdFieldName
SearchTermsRequiredByDefault($NewSetting=TRUE)
FilterOnSuppliedFunctions($Scores)
AddSynonyms($Word, $Synonyms)
Add synonyms.
const FIELDTYPE_DATERANGE
AddField($FieldName, $DBFieldName, $FieldType, $Weight, $UsedInKeywordSearch)
SearchEngine(&$DB, $ItemTableName, $ItemIdFieldName, $ReferenceTableName, $ReferenceSourceIdFieldName, $ReferenceDestinationIdFieldName)
GroupedSearch($SearchGroups, $StartingResult=0, $NumberOfResults=10, $SortByField=NULL, $SortDescending=TRUE)
FieldedSearch($SearchStrings, $StartingResult=0, $NumberOfResults=10, $SortByField=NULL, $SortDescending=TRUE)
FieldInKeywordSearch($FieldName)
FieldedSearchWeightScale($SearchStrings)
$NumberOfResultsAvailable
DefaultSearchLogic($NewSetting=NULL)
UpdateForItems($StartingItemId, $NumberOfItems)
GetFieldContent($ItemId, $FieldName)
AddResultFilterFunction($FunctionName)
SuggestAlternateSearches($SearchString)