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