]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/wikilens/RatingsUser.php
Remove rcs_id
[SourceForge/phpwiki.git] / lib / wikilens / RatingsUser.php
1 <?php //-*-php-*-
2 // $Id$
3 /* Copyright (C) 2004 Dan Frankowski
4  * Copyright (C) 2010 Roger Guignard, Alcatel-Lucent
5  *
6  * This file is part of PhpWiki.
7  *
8  * PhpWiki is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * PhpWiki is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with PhpWiki; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  */
22
23 require_once("lib/wikilens/RatingsDb.php");
24
25 /**
26  * Get a RatingsUser instance (possibly from a cache).
27  */
28 class RatingsUserFactory {
29     function & getUser($userid) {
30         //print "getUser($userid) ";
31         global $_ratingsUserCache;
32         if (!isset($_ratingsUserCache)) {
33             $_ratingsUserCache = array();
34         }
35         if (!array_key_exists($userid, $_ratingsUserCache)) {
36             //print "MISS ";
37             $_ratingsUserCache[$userid] = new RatingsUser($userid);
38         }
39         else {
40             //print "HIT ";
41         }
42         return $_ratingsUserCache[$userid];
43     }
44 }
45
46 /**
47  * This class represents a user that gets ratings
48  */
49 class RatingsUser {
50     var $_userid;
51     var $_ratings_loaded;
52     var $_ratings;
53     var $_num_ratings;
54     var $_mean_ratings;
55     var $_pearson_sims;
56
57     function RatingsUser($userid) {
58         $this->_userid = $userid;
59         $this->_ratings_loaded = false;
60         $this->_ratings = array();
61         $this->_num_ratings = 0;
62         $this->_mean_ratings = array();
63         $this->_pearson_sims = array();
64     }
65
66     function getId() {
67         return $this->_userid;
68     }
69
70     function & _get_rating_dbi() {
71         // This is a hack, because otherwise this object doesn't know about a
72         // DBI at all.  Perhaps all this ratings stuff should live somewhere
73         // else that's less of a base class.
74         if (isset($this->_rdbi))
75             return $this->_rdbi;
76         $this->_rdbi = RatingsDb::getTheRatingsDb();
77         return $this->_rdbi;
78     }
79
80     // XXX: may want to think about caching ratings in the PHP session 
81     // since a WikiUser is created for *every* access, in which case rate.php
82     // will want to change to use this object instead of direct db access
83
84     /**
85      * Check whether $user is allowed to view this user's ratings
86      * 
87      * @return bool True if $user can view this user's ratings, false otherwise
88      */
89     function allow_view_ratings($user)
90     {
91         return true;
92     }
93
94     /**
95      * Gets this user's ratings
96      * 
97      * @return array Assoc. array [page_name][dimension] = _UserRating object
98      */
99     function get_ratings()
100     {
101         $this->_load_ratings();
102         return $this->_ratings;
103     }
104
105     /**
106     * Gets this user's mean rating across a dimension
107     * 
108     * @return float Mean rating
109     */
110     function mean_rating($dimension = 0)
111     {
112         // use memoized result if available
113         if (isset($this->_mean_ratings[$dimension])) 
114         {
115             return $this->_mean_ratings[$dimension];
116         }
117
118         $ratings = $this->get_ratings();
119         $total = 0;
120         $n = 0;
121
122         // walk the ratings and aggregate those in this dimension
123         foreach($ratings as $page => $rating)
124         {
125             if (isset($rating[$dimension]))
126             {
127                 $total += $rating[$dimension]->get_rating();
128                 $n++;
129             }
130         }
131
132         // memoize and return result
133         $this->_mean_ratings[$dimension] = ($n == 0 ? 0 : $total / $n);
134         return $this->_mean_ratings[$dimension];
135     }
136
137     // Note: the following has_rated, get_rating, set_rating, and unset_rating
138     // methods are colossally inefficient as they do a full ratings load from
139     // the database before performing their intended operation -- as such, the
140     // rate.php script still uses the direct database methods (plus, it's very
141     // ephemeral and doesn't particularly care about the ratings count or any
142     // other features that these methods might provide down the road)
143
144     function has_rated($pagename, $dimension = null)
145     {
146         // XXX: does this really want to do a full ratings load?  (scalability?)
147         $this->_load_ratings();
148         if (isset($dimension))
149         {
150             
151
152             if (isset($this->_ratings[$pagename][$dimension]))
153             {
154                 return true;
155             }
156         }
157         else
158         {
159             if (isset($this->_ratings[$pagename]))
160             {
161                 return true;
162             }
163         }
164         return false;
165     }
166
167     function get_rating($pagename, $dimension = 0)
168     {
169         // XXX: does this really want to do a full ratings load?  (scalability?)
170         if (RATING_STORAGE == 'SQL')
171             $this->_load_ratings();
172         else {
173             $rdbi = $this->_get_rating_dbi();
174             return $rdbi->metadata_get_rating($this->getId(), $pagename, $dimension);
175         }
176
177         if ($this->has_rated($pagename, $dimension))
178         {
179             return $this->_ratings[$pagename][$dimension]->get_rating();
180         }
181         return false;
182     }
183
184     function set_rating($pagename, $dimension, $rating)
185     {
186         // XXX: does this really want to do a full ratings load?  (scalability?)
187         $this->_load_ratings();
188
189         // XXX: what to do on failure?
190         $dbi = $this->_get_rating_dbi();
191         if (!($dbi->rate($this->_userid, $pagename, $dimension, $rating)))
192         {
193             return;
194         }
195
196         if ($this->has_rated($pagename, $dimension))
197         {
198             $this->_ratings[$pagename][$dimension]->set_rating($rating);
199         }
200         else
201         {
202             $this->_num_ratings++;
203             $this->_ratings[$rating['pagename']][$rating['dimension']]
204               = new _UserRating($this->_userid, $pagename, $dimension, $rating);
205         }
206     }
207
208     function unset_rating($pagename, $dimension)
209     {
210         // XXX: does this really want to do a full ratings load?  (scalability?)
211         $this->_load_ratings();
212         if ($this->has_rated($pagename, $dimension))
213         {
214             // XXX: what to do on failure?
215             if ($this->_dbi->delete_rating($this->_userid,$pagename,$dimension))
216             {
217                 $this->_num_ratings--;
218                 unset($this->_ratings[$pagename][$dimension]);
219                 if (!count($this->_ratings[$pagename]))
220                 {
221                     unset($this->_ratings[$pagename]);
222                 }
223             }
224         }
225     }
226
227     function pearson_similarity($user, $dimension = 0)
228     {
229         // use memoized result if available
230         if (isset($this->_pearson_sims[$user->getId()][$dimension]))
231         {
232             return $this->_pearson_sims[$user->getId()][$dimension];
233         }
234     
235         $ratings1 = $this->get_ratings();
236         $mean1    = $this->mean_rating($dimension);
237         // XXX: sanify user input?
238         $ratings2 = $user->get_ratings();
239         $mean2    = $user->mean_rating($dimension);
240
241         // swap if it would speed things up a bit
242         if (count($ratings1) < count($ratings2))
243         {
244             $tmp = $ratings1;
245             $ratings1 = $ratings2;
246             $ratings2 = $tmp;
247             $tmp = $mean1;
248             $mean1 = $mean2;
249             $mean2 = $tmp;
250         }
251
252         list($sum11, $sum22, $sum12, $n) = array(0,0,0,0);
253
254         // compute sum(x*x), sum(y*y), sum(x*y) over co-rated items
255         foreach ($ratings1 as $page => $rating1)
256         {
257             if (isset($rating1[$dimension]) && isset($ratings2[$page]))
258             {
259                 $rating2 = $ratings2[$page];
260                 if (isset($rating2[$dimension]))
261                 {
262                     $r1 = $rating1[$dimension]->get_rating();
263                     $r2 = $rating2[$dimension]->get_rating();
264                     // print "co-rating with " . $user->getId() . " $page $r1 $r2<BR>";
265
266                     $r1 -= $mean1;
267                     $r2 -= $mean2;
268
269                     $sum11 += $r1 * $r1;
270                     $sum22 += $r2 * $r2;
271                     $sum12 += $r1 * $r2;
272                     $n++;
273                 }
274             }
275         }
276
277         // this returns both the computed similarity and the number of co-rated
278         // items that the similarity was based on
279
280         // prevent division-by-zero
281         if (sqrt($sum11) == 0 || sqrt($sum12) == 0) 
282             $sim = array(0, $n);
283         else
284         // Pearson similarity
285             $sim = array($sum12 / (sqrt($sum11) * sqrt($sum22)), $n);
286
287         // print "sim is " . $sim[0] . "<BR><BR>";
288
289         // memoize result
290         $this->_pearson_sims[$user->getId()][$dimension] = $sim;
291         return $this->_pearson_sims[$user->getId()][$dimension] = $sim;
292     }
293
294     function knn_uu_predict($pagename, &$neighbors, $dimension = 0)
295     {
296         /*
297         print "<PRE>";
298         var_dump($this->_pearson_sims);
299         var_dump($this->_ratings);
300         print "</PRE>";
301         print "pred for $pagename<BR>";
302         */
303         $total = 0;
304         $total_sim = 0;
305
306         // foreach($neighbors as $nbor)
307         // {
308         for($i = 0; $i < count($neighbors); $i++)
309         {
310             // more silly PHP references...
311             $nbor =& $neighbors[$i];
312
313             // ignore self-neighbor
314             if ($this->getId() == $nbor->getId())
315                 continue;
316
317             if ($nbor->has_rated($pagename, $dimension))
318             {
319                 list($sim, $n_items) = $this->pearson_similarity($nbor);
320                 // ignore absolute sims below 0.1, negative sims??
321                 // XXX: no filtering done... small-world = too few neighbors
322                 if (1 || ($sim > 0 && abs($sim) >= 0.1))
323                 {
324                     // n/50 sig weighting
325                     if ($n_items < 50)
326                         $sim *= $n_items / 50;
327                     /*
328                     print "neighbor is " . $nbor->getId() . "<BR>";
329                     print "weighted sim is " . $sim . "<BR>";
330                     print "dev from mean is " . ($nbor->get_rating($pagename, $dimension) - $nbor->mean_rating($dimension)) . "<BR>";
331                     */
332                     $total += $sim * ($nbor->get_rating($pagename, $dimension) - $nbor->mean_rating($dimension));
333                     $total_sim += abs($sim);
334                 }
335             }
336         }
337
338         $my_mean = $this->mean_rating($dimension);
339         /*
340         print "your mean is $my_mean<BR>";
341         print "pred dev from mean is " . ($total_sim == 0 ? -1 : ($total / $total_sim)) . "<BR>";
342         print "pred is " . ($total_sim == 0 ? -1 : ($total / $total_sim + $my_mean)) . "<BR><BR>";
343         */
344         // XXX: what to do if no neighbors have rated pagename?
345         return ($total_sim == 0 ? 0 : ($total / $total_sim + $my_mean));
346     }
347
348     function _load_ratings($force = false)
349     {
350         if (!$this->_ratings_loaded || $force)
351         {
352             // print "load " . $this->getId() . "<BR>";
353             $this->_ratings = array();
354             $this->_num_ratings = 0;
355             // only signed-in users have ratings (XXX: authenticated?)
356
357             // passing null as first parameter to indicate all dimensions
358             $dbi = $this->_get_rating_dbi();
359
360             //$rating_iter = $dbi->sql_get_rating(null, $this->_userid, null);
361             //($dimension=null, $rater=null, $ratee=null, $orderby = null, $pageinfo = "ratee")
362             $rating_iter = $dbi->get_rating_page(null, $this->_userid);
363
364             while($rating = $rating_iter->next())
365             {
366                 if (defined('FUSIONFORGE') and FUSIONFORGE) {
367                     $rating['pagename'] = preg_replace('/^'.PAGE_PREFIX.'/', '', $rating['pagename']);
368                 }
369                 $this->_num_ratings++;
370                 $this->_ratings[$rating['pagename']][$rating['dimension']]
371                   = new _UserRating($this->_userid, 
372                                     $rating['pagename'],
373                                     $rating['dimension'], 
374                                     $rating['ratingvalue']);
375             }
376
377             $this->_ratings_loaded = true;
378         }
379     }
380 }
381
382 /** Represent a rating. */
383 class _UserRating
384 {
385     function _UserRating ($rater, $ratee, $dimension, $rating) 
386     {
387         $this->rater     = (string)$rater;
388         $this->ratee     = (string)$ratee;
389         $this->dimension = (int)$dimension;
390         $this->rating    = (float)$rating;
391     }
392
393     function get_rater() 
394     {
395         return $this->rater;
396     }
397
398     function get_ratee() 
399     {
400         return $this->ratee;
401     }
402
403     function get_rating() 
404     {
405         return $this->rating;
406     }
407
408     function get_dimension() 
409     {
410         return $this->dimension;
411     }
412
413     function set_rater() 
414     {
415         $this->rater = (string)$rater;
416     }
417
418     function set_ratee() 
419     {
420         $this->ratee = (string)$ratee;
421     }
422
423     function set_rating() 
424     {
425         $this->rating = (float)$rating;
426     }
427
428     function set_dimension() 
429     {
430         $this->dimension = (int)$dimension;
431     }
432 }
433
434 // Local Variables:
435 // mode: php
436 // tab-width: 8
437 // c-basic-offset: 4
438 // c-hanging-comment-ender-p: nil
439 // indent-tabs-mode: nil
440 // End:
441 ?>