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