CWIS Developer Documentation
Recommender.php
Go to the documentation of this file.
1 <?PHP
2 #
3 # FILE: Recommender.php
4 #
5 # Part of the Collection Workflow Integration System (CWIS)
6 # Copyright 2004-2017 Edward Almasy and Internet Scout Research Group
7 # http://scout.wisc.edu/cwis/
8 #
9 
14 {
15 
16  # ---- PUBLIC INTERFACE --------------------------------------------------
17  # define content field types
23 
41  public function __construct(&$DB, $ItemTableName, $RatingTableName,
42  $ItemIdFieldName, $UserIdFieldName, $RatingFieldName,
43  $ContentFields)
44  {
45  # set default parameters
46  $this->ContentCorrelationThreshold = 1;
47 
48  # save database object
49  $this->DB =& $DB;
50 
51  # save new configuration values
52  $this->ItemTableName = $ItemTableName;
53  $this->RatingTableName = $RatingTableName;
54  $this->ItemIdFieldName = $ItemIdFieldName;
55  $this->UserIdFieldName = $UserIdFieldName;
56  $this->RatingFieldName = $RatingFieldName;
57  $this->ContentFields = $ContentFields;
58 
59  # set default debug state
60  $this->DebugLevel = 0;
61  }
62 
67  public function DebugLevel($Setting)
68  {
69  $this->DebugLevel = $Setting;
70  }
71 
72 
73  # ---- recommendation methods
74 
83  public function Recommend($UserId, $StartingResult = 0, $NumberOfResults = 10)
84  {
85  if ($this->DebugLevel > 0)
86  {
87  print "REC: Recommend(${UserId}, ${StartingResult},"
88  ." ${NumberOfResults})<br>\n";
89  }
90 
91  # load in user ratings
92  $Ratings = array();
93  $DB =& $this->DB;
94  $DB->Query("SELECT ".$this->ItemIdFieldName.", ".$this->RatingFieldName
95  ." FROM ".$this->RatingTableName
96  ." WHERE ".$this->UserIdFieldName." = ${UserId}");
97  while ($Row = $DB->FetchRow())
98  {
99  $Ratings[$Row[$this->ItemIdFieldName]] =
100  $Row[$this->RatingFieldName];
101  }
102  if ($this->DebugLevel > 1)
103  {
104  print "REC: user has rated ".count($Ratings)." items<br>\n";
105  }
106 
107  # for each item that user has rated
108  $RecVals = array();
109  foreach ($Ratings as $ItemId => $ItemRating)
110  {
111  # for each content correlation available for that item
112  $DB->Query("SELECT Correlation, ItemIdB "
113  ."FROM RecContentCorrelations "
114  ."WHERE ItemIdA = ${ItemId}");
115  while ($Row = $DB->FetchRow())
116  {
117  # multiply that correlation by normalized rating and add
118  # resulting value to recommendation value for that item
119  if (isset($RecVals[$Row["ItemIdB"]]))
120  {
121  $RecVals[$Row["ItemIdB"]] +=
122  $Row["Correlation"] * ($ItemRating - 50);
123  }
124  else
125  {
126  $RecVals[$Row["ItemIdB"]] =
127  $Row["Correlation"] * ($ItemRating - 50);
128  }
129  if ($this->DebugLevel > 9)
130  {
131  print "REC: RecVal[".$Row["ItemIdB"]."] = "
132  .$RecVals[$Row["ItemIdB"]]."<br>\n";
133  }
134  }
135  }
136  if ($this->DebugLevel > 1)
137  {
138  print "REC: found ".count($RecVals)." total recommendations<br>\n";
139  }
140 
141  # calculate average correlation between items
142  $ResultThreshold = $DB->Query("SELECT AVG(Correlation) "
143  ."AS Average FROM RecContentCorrelations", "Average");
144  $ResultThreshold = round($ResultThreshold) * 2;
145 
146  # for each recommended item
147  foreach ($RecVals as $ItemId => $RecVal)
148  {
149  # remove item from list if user already rated it
150  if (isset($Ratings[$ItemId]))
151  {
152  unset($RecVals[$ItemId]);
153  }
154  else
155  {
156  # scale recommendation value back to match thresholds
157  $RecVals[$ItemId] = round($RecVal / 50);
158 
159  # remove item from recommendation list if value is below threshold
160  if ($RecVals[$ItemId] < $ResultThreshold)
161  {
162  unset($RecVals[$ItemId]);
163  }
164  }
165  }
166  if ($this->DebugLevel > 1)
167  {
168  print "REC: found ".count($RecVals)." positive recommendations<br>\n";
169  }
170 
171  # sort recommendation list by value
172  if (isset($RecVals)) { arsort($RecVals, SORT_NUMERIC); }
173 
174  # save total number of results available
175  $this->NumberOfResultsAvailable = count($RecVals);
176 
177  # trim result list to match range requested by caller
178  $RecValKeys = array_slice(
179  array_keys($RecVals), $StartingResult, $NumberOfResults);
180  $RecValSegment = array();
181  foreach ($RecValKeys as $Key)
182  {
183  $RecValSegment[$Key] = $RecVals[$Key];
184  }
185 
186  # return recommendation list to caller
187  return $RecValSegment;
188  }
189 
195  public function AddResultFilterFunction($FunctionName)
196  {
197  # save filter function name
198  $this->FilterFuncs[] = $FunctionName;
199  }
200 
205  public function NumberOfResults()
206  {
207  return $this->NumberOfResultsAvailable;
208  }
209 
214  public function SearchTime()
215  {
216  return $this->LastSearchTime;
217  }
218 
228  public function GetSourceList($UserId, $RecommendedItemId)
229  {
230  # pull list of correlations from DB
231  $this->DB->Query("SELECT * FROM RecContentCorrelations, ".$this->RatingTableName
232  ." WHERE (ItemIdA = ${RecommendedItemId}"
233  ." OR ItemIdB = ${RecommendedItemId})"
234  ." AND ".$this->UserIdFieldName." = ".$UserId
235  ." AND (RecContentCorrelations.ItemIdA = "
236  .$this->RatingTableName.".".$this->ItemIdFieldName
237  ." OR RecContentCorrelations.ItemIdB = "
238  .$this->RatingTableName.".".$this->ItemIdFieldName.")"
239  ." AND Rating >= 50 "
240  ." ORDER BY Correlation DESC");
241 
242  # for each correlation
243  $SourceList = array();
244  while ($Row = $this->DB->FetchRow())
245  {
246  # pick out appropriate item ID
247  if ($Row["ItemIdA"] == $RecommendedItemId)
248  {
249  $ItemId = $Row["ItemIdB"];
250  }
251  else
252  {
253  $ItemId = $Row["ItemIdA"];
254  }
255 
256  # add item to recommendation source list
257  $SourceList[$ItemId] = $Row["Correlation"];
258  }
259 
260  # return recommendation source list to caller
261  return $SourceList;
262  }
263 
271  public function FindSimilarItems($ItemId, $FieldList = NULL)
272  {
273  if ($this->DebugLevel > 1)
274  {
275  print "REC: searching for items similar to item \""
276  .$ItemId."\"<br>\n";
277  }
278 
279  # make sure we have item IDs available
280  $this->LoadItemIds();
281 
282  # start with empty array
283  $SimilarItems = array();
284 
285  # for every item
286  foreach ($this->ItemIds as $Id)
287  {
288  # if item is not specified item
289  if ($Id != $ItemId)
290  {
291  # calculate correlation of item to specified item
292  $Correlation = $this->CalculateContentCorrelation(
293  $ItemId, $Id, $FieldList);
294 
295  # if correlation is above threshold
296  if ($Correlation > $this->ContentCorrelationThreshold)
297  {
298  # add item to list of similar items
299  $SimilarItems[$Id] = $Correlation;
300  }
301  }
302  }
303  if ($this->DebugLevel > 3)
304  {
305  print "REC: ".count($SimilarItems)." similar items to item \""
306  .$ItemId."\" found<br>\n";
307  }
308 
309  # filter list of similar items (if any)
310  if (count($SimilarItems) > 0)
311  {
312  $SimilarItems = $this->FilterOnSuppliedFunctions($SimilarItems);
313  if ($this->DebugLevel > 4)
314  {
315  print "REC: ".count($SimilarItems)." similar items to item \""
316  .$ItemId."\" left after filtering<br>\n";
317  }
318  }
319 
320  # if any similar items left
321  if (count($SimilarItems) > 0)
322  {
323  # sort list of similar items in order of most to least similar
324  arsort($SimilarItems, SORT_NUMERIC);
325  }
326 
327  # return list of similar items to caller
328  return $SimilarItems;
329  }
330 
339  public function RecommendFieldValues($ItemId, $FieldList = NULL)
340  {
341  if ($this->DebugLevel > 1)
342  {
343  print "REC: generating field value recommendations for item \""
344  .$ItemId."\"<br>\n";
345  }
346 
347  # start with empty array of values
348  $RecVals = array();
349 
350  # generate list of similar items
351  $SimilarItems = $this->FindSimilarItems($ItemId, $FieldList);
352 
353  # if similar items found
354  if (count($SimilarItems) > 0)
355  {
356  # prune list of similar items to only top third of better-than-average
357  $AverageCorr = intval(array_sum($SimilarItems) / count($SimilarItems));
358  reset($SimilarItems);
359  $HighestCorr = current($SimilarItems);
360  $CorrThreshold = intval($HighestCorr - (($HighestCorr - $AverageCorr) / 3));
361  if ($this->DebugLevel > 8)
362  {
363  print "REC: <i>Average Correlation: $AverageCorr"
364  ." &nbsp;&nbsp;&nbsp;&nbsp; Highest Correlation:"
365  ." $HighestCorr &nbsp;&nbsp;&nbsp;&nbsp; Correlation"
366  ." Threshold: $CorrThreshold </i><br>\n";
367  }
368  foreach ($SimilarItems as $ItemId => $ItemCorr)
369  {
370  if ($ItemCorr < $CorrThreshold)
371  {
372  unset($SimilarItems[$ItemId]);
373  }
374  }
375  if ($this->DebugLevel > 6)
376  {
377  print "REC: ".count($SimilarItems)
378  ." similar items left after threshold pruning<br>\n";
379  }
380 
381  # for each item
382  foreach ($SimilarItems as $SimItemId => $SimItemCorr)
383  {
384  # for each field
385  foreach ($this->ContentFields as $FieldName => $FieldAttributes)
386  {
387  # load field data for this item
388  $FieldData = $this->GetFieldValue($SimItemId, $FieldName);
389 
390  # if field data is array
391  if (is_array($FieldData))
392  {
393  # for each field data value
394  foreach ($FieldData as $FieldDataVal)
395  {
396  # if data value is not empty
397  $FieldDataVal = trim($FieldDataVal);
398  if (strlen($FieldDataVal) > 0)
399  {
400  # increment count for data value
401  $RecVals[$FieldName][$FieldDataVal]++;
402  }
403  }
404  }
405  else
406  {
407  # if data value is not empty
408  $FieldData = trim($FieldData);
409  if (strlen($FieldData) > 0)
410  {
411  # increment count for data value
412  $RecVals[$FieldName][$FieldData]++;
413  }
414  }
415  }
416  }
417 
418  # for each field
419  $MatchingCountThreshold = 3;
420  foreach ($RecVals as $FieldName => $FieldVals)
421  {
422  # determine cutoff threshold
423  arsort($FieldVals, SORT_NUMERIC);
424  reset($FieldVals);
425  $HighestCount = current($FieldVals);
426  $AverageCount = intval(array_sum($FieldVals) / count($FieldVals));
427  $CountThreshold = intval($AverageCount
428  + (($HighestCount - $AverageCount) / 2));
429  if ($CountThreshold < $MatchingCountThreshold)
430  {
431  $CountThreshold = $MatchingCountThreshold;
432  }
433  if ($this->DebugLevel > 8)
434  {
435  print "REC: <i>Field: $FieldName &nbsp;&nbsp;&nbsp;&nbsp;"
436  ." Average Count: $AverageCount &nbsp;&nbsp;&nbsp;&nbsp;"
437  ." Highest Count: $HighestCount &nbsp;&nbsp;&nbsp;&nbsp;"
438  ." Count Threshold: $CountThreshold </i><br>\n";
439  }
440 
441  # for each field data value
442  foreach ($FieldVals as $FieldVal => $FieldValCount)
443  {
444  # if value count is below threshold
445  if ($FieldValCount < $CountThreshold)
446  {
447  # unset value
448  unset($RecVals[$FieldName][$FieldVal]);
449  }
450  }
451 
452  if ($this->DebugLevel > 3)
453  {
454  print "REC: found ".count($RecVals[$FieldName])
455  ." recommended values for field \""
456  .$FieldName."\" after threshold pruning<br>\n";
457  }
458  }
459  }
460 
461  # return recommended values to caller
462  return $RecVals;
463  }
464 
465 
466  # ---- database update methods
467 
474  public function UpdateForItems($StartingItemId, $NumberOfItems)
475  {
476  if ($this->DebugLevel > 0)
477  {
478  print "REC: UpdateForItems(${StartingItemId},"
479  ." ${NumberOfItems})<br>\n";
480  }
481  # make sure we have item IDs available
482  $this->LoadItemIds();
483 
484  # for every item
485  $ItemsUpdated = 0;
486  $ItemId = NULL;
487  foreach ($this->ItemIds as $ItemId)
488  {
489  # if item ID is within requested range
490  if ($ItemId >= $StartingItemId)
491  {
492  # update recommender info for item
493  if ($this->DebugLevel > 1)
494  { print("REC: doing item ${ItemId}<br>\n"); }
495  $this->UpdateForItem($ItemId, TRUE);
496  $ItemsUpdated++;
497 
498  # if we have done requested number of items
499  if ($ItemsUpdated >= $NumberOfItems)
500  {
501  # bail out
502  if ($this->DebugLevel > 1)
503  {
504  print "REC: bailing out with item ${ItemId}<br>\n";
505  }
506  return $ItemId;
507  }
508  }
509  }
510 
511  # return ID of last item updated to caller
512  return $ItemId;
513  }
514 
521  public function UpdateForItem($ItemId, $FullPass = FALSE)
522  {
523  if ($this->DebugLevel > 1)
524  {
525  print "REC: updating for item \"".$ItemId."\"<br>\n";
526  }
527 
528  # make sure we have item IDs available
529  $this->LoadItemIds();
530 
531  # clear existing correlations for this item
532  $this->DB->Query("DELETE FROM RecContentCorrelations "
533  ."WHERE ItemIdA = ${ItemId}");
534 
535  # for every item
536  foreach ($this->ItemIds as $Id)
537  {
538  # if full pass and item is later in list than current item
539  if (($FullPass == FALSE) || ($Id > $ItemId))
540  {
541  # update correlation value for item and target item
542  $this->UpdateContentCorrelation($ItemId, $Id);
543  }
544  }
545  }
546 
551  public function DropItem($ItemId)
552  {
553  # drop all correlation entries referring to item
554  $this->DB->Query("DELETE FROM RecContentCorrelations "
555  ."WHERE ItemIdA = ".$ItemId." "
556  ."OR ItemIdB = ".$ItemId);
557  }
558 
562  public function PruneCorrelations()
563  {
564  # get average correlation
565  $AverageCorrelation = $this->DB->Query("SELECT AVG(Correlation) "
566  ."AS Average FROM RecContentCorrelations", "Average");
567 
568  # dump all below-average correlations
569  if ($AverageCorrelation > 0)
570  {
571  $this->DB->Query("DELETE FROM RecContentCorrelations "
572  ."WHERE Correlation <= ${AverageCorrelation}");
573  }
574  }
575 
580  public function GetItemIds()
581  {
582  if (self::$ItemIdCache === NULL)
583  {
584  $this->DB->Query("SELECT ".$this->ItemIdFieldName." AS Id FROM "
585  .$this->ItemTableName." ORDER BY ".$this->ItemIdFieldName);
586  self::$ItemIdCache = $this->DB->FetchColumn("Id");
587  }
588  return self::$ItemIdCache;
589  }
590 
595  static public function ClearCaches()
596  {
597  self::$CorrelationCache = NULL;
598  self::$ItemIdCache = NULL;
599  self::$ItemDataCache = NULL;
600  }
601 
602 
603  # ---- PRIVATE INTERFACE -------------------------------------------------
604 
605  private $ContentCorrelationThreshold;
606  private $ContentFields;
607  private $ItemTableName;
608  private $RatingTableName;
609  private $ItemIdFieldName;
610  private $UserIdFieldName;
611  private $RatingFieldName;
612  private $ItemIds;
613  private $DB;
614  private $FilterFuncs;
615  private $LastSearchTime;
616  private $NumberOfResultsAvailable;
617  private $DebugLevel;
618 
619  static private $ItemIdCache = NULL;
620  static private $ItemDataCache = NULL;
621  static private $CorrelationCache = NULL;
622 
626  protected function LoadItemIds()
627  {
628  # if item IDs not already loaded
629  if (!isset($this->ItemIds))
630  {
631  # load item IDs from DB
632  $this->DB->Query("SELECT ".$this->ItemIdFieldName." AS Id FROM "
633  .$this->ItemTableName." ORDER BY ".$this->ItemIdFieldName);
634  $this->ItemIds[] = $this->DB->FetchColumn("Id");
635  }
636  }
637 
644  protected function GetFieldData($ItemId, $FieldName)
645  {
646  # if data not already loaded
647  if (!isset(self::$ItemDataCache[$ItemId][$FieldName]))
648  {
649  # load field value from DB
650  $FieldValue = $this->GetFieldValue($ItemId, $FieldName);
651 
652  # if field value is array
653  if (is_array($FieldValue))
654  {
655  # concatenate together text from array elements
656  $FieldValue = implode(" ", $FieldValue);
657  }
658 
659  # normalize text and break into word array
660  self::$ItemDataCache[$ItemId][$FieldName] =
661  $this->NormalizeAndParseText($FieldValue);
662  }
663 
664  # return cached data to caller
665  return self::$ItemDataCache[$ItemId][$FieldName];
666  }
667 
676  protected function CalculateContentCorrelation($ItemIdA, $ItemIdB, $FieldList = NULL)
677  {
678  if ($this->DebugLevel > 10) { print("REC: calculating correlation"
679  ." between items $ItemIdA and $ItemIdB<br>\n"); }
680 
681  # order item ID numbers
682  if ($ItemIdA > $ItemIdB)
683  {
684  $Temp = $ItemIdA;
685  $ItemIdA = $ItemIdB;
686  $ItemIdB = $Temp;
687  }
688 
689  # if we already have the correlation
690  if (isset(self::$CorrelationCache[$ItemIdA][$ItemIdB]))
691  {
692  # retrieve correlation from cache
693  $TotalCorrelation = self::$CorrelationCache[$ItemIdA][$ItemIdB];
694  }
695  else
696  {
697  # if list of fields to correlate specified
698  if ($FieldList != NULL)
699  {
700  # create list with only specified fields
701  foreach ($FieldList as $FieldName)
702  {
703  $ContentFields[$FieldName] = $this->ContentFields[$FieldName];
704  }
705  }
706  else
707  {
708  # use all fields
709  $ContentFields = $this->ContentFields;
710  }
711 
712  # for each content field
713  $TotalCorrelation = 0;
714  foreach ($ContentFields as $FieldName => $FieldAttributes)
715  {
716  # if field is of a type that we use for correlation
717  $FieldType = intval($FieldAttributes["FieldType"]);
718  if (($FieldType == self::CONTENTFIELDTYPE_TEXT)
719  || ($FieldType == self::CONTENTFIELDTYPE_CONTROLLEDNAME))
720  {
721  # load data
722  $ItemAData = $this->GetFieldData($ItemIdA, $FieldName);
723  $ItemBData = $this->GetFieldData($ItemIdB, $FieldName);
724  if ($this->DebugLevel > 15)
725  {
726  print "REC: loaded ".count($ItemAData)
727  ." terms for item #".$ItemIdA." and "
728  .count($ItemBData)." terms for item #"
729  .$ItemIdB." for field \"".$FieldName."\"<br>\n";
730  }
731 
732  # call appropriate routine to get correlation
733  switch ($FieldType)
734  {
735  case self::CONTENTFIELDTYPE_TEXT:
736  case self::CONTENTFIELDTYPE_CONTROLLEDNAME:
737  $Correlation = $this->CalcTextCorrelation(
738  $ItemAData, $ItemBData);
739  break;
740  }
741 
742  # add correlation multiplied by weight to total
743  $TotalCorrelation += $Correlation * $FieldAttributes["Weight"];
744  }
745  }
746 
747  # store correlation to cache
748  self::$CorrelationCache[$ItemIdA][$ItemIdB] = $TotalCorrelation;
749  }
750 
751  # return correlation value to caller
752  if ($this->DebugLevel > 9)
753  {
754  print("REC: correlation between items $ItemIdA and $ItemIdB"
755  ." found to be $TotalCorrelation<br>\n");
756  }
757  return $TotalCorrelation;
758  }
759 
765  protected function UpdateContentCorrelation($ItemIdA, $ItemIdB)
766  {
767  if ($this->DebugLevel > 6) { print("REC: updating correlation between"
768  ." items $ItemIdA and $ItemIdB<br>\n"); }
769 
770  # bail out if two items are the same
771  if ($ItemIdA == $ItemIdB) { return; }
772 
773  # calculate correlation
774  $Correlation = $this->CalculateContentCorrelation($ItemIdA, $ItemIdB);
775 
776  # save new correlation
777  $this->ContentCorrelation($ItemIdA, $ItemIdB, $Correlation);
778  }
779 
785  protected function NormalizeAndParseText($Text)
786  {
787  $StopWords = array(
788  "a",
789  "about",
790  "also",
791  "an",
792  "and",
793  "are",
794  "as",
795  "at",
796  "be",
797  "but",
798  "by",
799  "can",
800  "each",
801  "either",
802  "for",
803  "from",
804  "has",
805  "he",
806  "her",
807  "here",
808  "hers",
809  "him",
810  "his",
811  "how",
812  "i",
813  "if",
814  "in",
815  "include",
816  "into",
817  "is",
818  "it",
819  "its",
820  "me",
821  "neither",
822  "no",
823  "nor",
824  "not",
825  "of",
826  "on",
827  "or",
828  "so",
829  "she",
830  "than",
831  "that",
832  "the",
833  "their",
834  "them",
835  "then",
836  "there",
837  "these",
838  "they",
839  "this",
840  "those",
841  "through",
842  "to",
843  "too",
844  "very",
845  "what",
846  "when",
847  "where",
848  "while",
849  "who",
850  "why",
851  "will",
852  "you",
853  "");
854 
855  # strip any HTML tags
856  $Text = strip_tags($Text);
857 
858  # strip any punctuation
859  $Text = preg_replace("/,\\.\\?-\\(\\)\\[\\]\"/", " ", $Text); # "
860 
861  # normalize whitespace
862  $Text = trim(preg_replace("/[\\s]+/", " ", $Text));
863 
864  # convert to all lower case
865  $Text = strtolower($Text);
866 
867  # split text into arrays of words
868  $Words = explode(" ", $Text);
869 
870  # filter out all stop words
871  $Words = array_diff($Words, $StopWords);
872 
873  # return word array to caller
874  return $Words;
875  }
876 
883  protected function CalcTextCorrelation($WordsA, $WordsB)
884  {
885  # get array containing intersection of two word arrays
886  $IntersectWords = array_intersect($WordsA, $WordsB);
887 
888  # return number of words remaining as score
889  return count($IntersectWords);
890  }
891 
899  protected function ContentCorrelation($ItemIdA, $ItemIdB, $NewCorrelation = -1)
900  {
901  # if item ID A is greater than item ID B
902  if ($ItemIdA > $ItemIdB)
903  {
904  # swap item IDs
905  $Temp = $ItemIdA;
906  $ItemIdA = $ItemIdB;
907  $ItemIdB = $Temp;
908  }
909 
910  # if new correlation value provided
911  if ($NewCorrelation != -1)
912  {
913  # if new value is above threshold
914  if ($NewCorrelation >= $this->ContentCorrelationThreshold)
915  {
916  # insert new correlation value in DB
917  $this->DB->Query("INSERT INTO RecContentCorrelations "
918  ."(ItemIdA, ItemIdB, Correlation) "
919  ."VALUES (${ItemIdA}, ${ItemIdB}, ${NewCorrelation})");
920 
921  # return correlation value is new value
922  $Correlation = $NewCorrelation;
923  }
924  # else
925  else
926  {
927  # return value is zero
928  $Correlation = 0;
929  }
930  }
931  else
932  {
933  # retrieve correlation value from DB
934  $Correlation = $this->DB->Query(
935  "SELECT Correlation FROM RecContentCorrelations "
936  ."WHERE ItemIdA = ${ItemIdA} AND ItemIdB = ${ItemIdB}",
937  "Correlation");
938 
939  # if no value found in DB
940  if ($Correlation == FALSE)
941  {
942  # return value is zero
943  $Correlation = 0;
944  }
945  }
946 
947  # return correlation value to caller
948  return $Correlation;
949  }
950 
956  protected function FilterOnSuppliedFunctions($Results)
957  {
958  # if filter functions have been set
959  if (count($this->FilterFuncs) > 0)
960  {
961  # for each result
962  foreach ($Results as $ResourceId => $Result)
963  {
964  # for each filter function
965  foreach ($this->FilterFuncs as $FuncName)
966  {
967  # if filter protected function return TRUE for result resource
968  if ($FuncName($ResourceId))
969  {
970  # discard result
971  if ($this->DebugLevel > 2)
972  {
973  print("REC: filter callback rejected resource"
974  ." ${ResourceId}<br>\n");
975  }
976  unset($Results[$ResourceId]);
977 
978  # bail out of filter func loop
979  continue 2;
980  }
981  }
982  }
983  }
984 
985  # return filtered list to caller
986  return $Results;
987  }
988 }
989 
DebugLevel($Setting)
Set level for debugging output.
Definition: Recommender.php:67
RecommendFieldValues($ItemId, $FieldList=NULL)
Dynamically generate and return list of recommended field values for item.
UpdateForItems($StartingItemId, $NumberOfItems)
Update recommender data for range of items.
GetSourceList($UserId, $RecommendedItemId)
Return list of items used to generate recommendation of specified item.
ContentCorrelation($ItemIdA, $ItemIdB, $NewCorrelation=-1)
Get/set stored value for correlation between two items.
const CONTENTFIELDTYPE_CONTROLLEDNAME
Definition: Recommender.php:20
AddResultFilterFunction($FunctionName)
Add function to be called to filter returned recommendation list.
const CONTENTFIELDTYPE_DATE
Definition: Recommender.php:21
__construct(&$DB, $ItemTableName, $RatingTableName, $ItemIdFieldName, $UserIdFieldName, $RatingFieldName, $ContentFields)
Object constructor.
Definition: Recommender.php:41
FilterOnSuppliedFunctions($Results)
Run results through supplied filter functions.
SearchTime()
Get time it took to generate the most recent recommendation.
PruneCorrelations()
Prune any stored correlation values that are below-average.
GetItemIds()
Retrieve all item IDs.
UpdateForItem($ItemId, $FullPass=FALSE)
Update recommender data for specified item.
NumberOfResults()
Get number of recommendations generated.
const CONTENTFIELDTYPE_NUMERIC
Definition: Recommender.php:19
GetFieldData($ItemId, $FieldName)
Get data for field.
UpdateContentCorrelation($ItemIdA, $ItemIdB)
Calculate content correlation between two items and update in DB.
Recommend($UserId, $StartingResult=0, $NumberOfResults=10)
Recommend items for specified user.
Definition: Recommender.php:83
CalcTextCorrelation($WordsA, $WordsB)
Get value for correlation between two sets of words.
LoadItemIds()
Load internal item ID cache (if not already loaded).
const CONTENTFIELDTYPE_TEXT
Definition: Recommender.php:18
FindSimilarItems($ItemId, $FieldList=NULL)
Dynamically generate and return list of items similar to specified item.
const CONTENTFIELDTYPE_DATERAMGE
Definition: Recommender.php:22
NormalizeAndParseText($Text)
Normalize text string and parse into words.
CalculateContentCorrelation($ItemIdA, $ItemIdB, $FieldList=NULL)
Calculate content correlation between two items and return value to caller.
Recommendation engine.
Definition: Recommender.php:13
DropItem($ItemId)
Drop item from stored recommender data.
static ClearCaches()
Clear internal caches of item and correlation data.