]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/plugin/RateIt.php
shorter javascript,
[SourceForge/phpwiki.git] / lib / plugin / RateIt.php
1 <?php // -*-php-*-
2 rcs_id('$Id: RateIt.php,v 1.2 2004-03-31 06:22:22 rurban Exp $');
3 /*
4  Copyright 2004 $ThePhpWikiProgrammingTeam
5
6  This file is (not yet) 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 //define('RATING_STORAGE','WIKIPAGE');
24 define('RATING_STORAGE','SQL');
25 // leave undefined for internal, slow php engine.
26 define('RATING_EXTERNAL',PHPWIKI_DIR . 'suggest.exe');
27
28 /**
29  * RateIt: A recommender system, based on MovieLens and suggest.
30  * Store user ratings per pagename. The wikilens theme displays a navbar image bar
31  * with some nice javascript magic and this plugin shows various recommendations.
32  *
33  * There should be two methods to store ratings:
34  * In a SQL database as in wikilens http://dickens.cs.umn.edu/dfrankow/wikilens
35  *
36  * The most important fact: A page has more than one rating. There can
37  * be (and will be!) many ratings per page (ratee): different raters
38  * (users), in different dimensions. Are those stored per page
39  * (ratee)? Then what if I wish to access the ratings per rater
40  * (user)? 
41  * wikilens plans several user-centered applications like:
42  * a) show my ratings
43  * b) show my buddies' ratings
44  * c) show how my ratings are like my buddies'
45  * d) show where I agree/disagree with my buddy
46  * e) show what this group of people agree/disagree on
47  *
48  * If the ratings are stored in a real DB in a table, we can index the
49  * ratings by rater and ratee, and be confident in
50  * performance. Currently MovieLens has 80,000 users, 7,000 items,
51  * 10,000,000 ratings. This is an average of 1400 ratings/page if each
52  * page were rated equally. However, they're not: the most popular
53  * things have tens of thousands of ratings (e.g., "Pulp Fiction" has
54  * 42,000 ratings). If ratings are stored per page, you would have to
55  * save/read huge page metadata every time someone submits a
56  * rating. Finally, the movie domain has an unusually small number of
57  * items-- I'd expect a lot more in music, for example.
58  *
59  * For a simple rating system one can also store the rating in the page 
60  * metadata (default).
61  *
62  * Usage:    <?plugin RateIt ?>              to enable rating on this page
63  *   Note: The wikilens theme must be enabled, to enable this plugin!
64  *   Or use a sidebar based theme with the box method.
65  *           <?plugin RateIt show=ratings ?> to show my ratings
66  *           <?plugin RateIt show=buddies ?> to show my buddies
67  *           <?plugin RateIt show=ratings dimension=1 ?>
68  *
69  * @author:  Dan Frankowski (wikilens author), Reini Urban (as plugin)
70  *
71  * TODO: 
72  * - fix smart caching
73  * - finish mysuggest.c (external engine with data from mysql)
74  * - add php_prediction
75  * - add the various show modes (esp. TopN queries in PHP)
76  */
77 /*
78  CREATE TABLE rating (
79         dimension INT(4) NOT NULL,
80         raterpage INT(11) NOT NULL,
81         rateepage INT(11) NOT NULL,
82         ratingvalue FLOAT NOT NULL,
83         rateeversion INT(11) NOT NULL,
84         tstamp TIMESTAMP(14) NOT NULL,
85         PRIMARY KEY (dimension, raterpage, rateepage)
86  );
87 */
88
89 require_once("lib/WikiPlugin.php");
90
91 class WikiPlugin_RateIt
92 extends WikiPlugin
93 {
94     function getName() {
95         return _("RateIt");
96     }
97     function getDescription() {
98         return _("Recommendation system. Store user ratings per page");
99     }
100     function getVersion() {
101         return preg_replace("/[Revision: $]/", '',
102                             "\$Revision: 1.2 $");
103     }
104
105     function RatingWidgetJavascript() {
106         global $Theme;
107         $img   = substr($Theme->_findData("images/RateItNk0.png"),0,-7);
108         $urlprefix = WikiUrl("",0,1);
109         $js = "
110 function displayRating(imgPrefix, ratingvalue, pred) {
111   var cancel = imgPrefix + 'Cancel';
112   for (i=1; i<=10; i++) {
113     var imgName = imgPrefix + i;
114     var imgSrc = '".$img."';
115     if (pred)
116       document[imgName].title = '"._("Predicted value ")."'+ratingvalue;
117     else    
118       document[imgName].title = '"._("Your rating ")."'+ratingvalue;
119     if (i<=(ratingvalue*2)) {
120       if (pred)
121         document[imgName].src = imgSrc + ((i%2) ? 'Rk1' : 'Rk0') + '.png';
122       else
123         document[imgName].src = imgSrc + ((i%2) ? 'Ok1' : 'Ok0') + '.png';
124     } else {
125       document[imgName].src = imgSrc + ((i%2) ? 'Nk1' : 'Nk0') + '.png';
126     }
127   }
128   document[cancel].src = imgSrc + 'Cancel' + ((ratingvalue) ? '.png' : 'N.png');
129 }
130 function click(actionImg, pagename, version, imgPrefix, dimension, rating) {
131   if (rating == 'X') {
132     deleteRating(actionImg, pagename, dimension);
133     displayRating(imgPrefix, 0, 0);
134   } else {
135     submitRating(actionImg, pagename, version, dimension, rating);
136     displayRating(imgPrefix, rating, 0);
137   }
138 }
139 function submitRating(actionImg, page, version, dimension, rating) {
140   var myRand = Math.round(Math.random()*(1000000));
141   var imgSrc = page + '?version=' + version + '&action=".urlencode(_("RateIt"))."&mode=add&rating=' + rating + '&dimension=' + dimension + '&nopurge=cache&rand=' + myRand;
142   //alert('submitRating(' + page + ', ' + version + ', ' + dimension + ', ' + rating + ') => '+imgSrc);
143   document[actionImg].src= imgSrc;
144 }
145 function deleteRating(actionImg, page, dimension) {
146   var myRand = Math.round(Math.random()*(1000000));
147   var imgSrc = '".$urlprefix."' + page + '?action=".urlencode(_("RateIt"))."&mode=delete&dimension=' + dimension + '&nopurge=cache&rand=' + myRand;
148   //alert('deleteRating(' + page + ', ' + version + ', ' + dimension + ')');
149   document[actionImg].src= imgSrc;
150 }
151 ";
152         return JavaScript($js);
153     }
154
155     function actionImgPath() {
156         global $Theme;
157         return $Theme->_findFile("images/RateItAction.png");
158     }
159
160     /**
161      * Take a string and quote it sufficiently to be passed as a Javascript
162      * string between ''s
163      */
164     function _javascript_quote_string($s) {
165         return str_replace("'", "\'", $s);
166     }
167
168     function getDefaultArguments() {
169         return array( 'pagename'  => '[pagename]',
170                       'version'   => false,
171                       'id'        => 'rateit',
172                       'imgPrefix' => '',
173                       'dimension' => false,
174                       'small'     => false,
175                       'show'      => false,
176                       'mode'      => false,
177                       );
178     }
179
180     function head() { // early side-effects (before body)
181         global $Theme;
182         $Theme->addMoreHeaders($this->RatingWidgetJavascript());
183     }
184
185     // todo: only for signed users
186     // todo: set rating dbi for external rating database
187     function run($dbi, $argstr, $request, $basepage) {
188         global $Theme;
189         $this->_request = & $request;
190         $this->_dbi = & $dbi;
191         $user = & $request->getUser();
192         $this->userid = $user->UserName();
193         $args = $this->getArgs($argstr, $request);
194         $this->dimension = $args['dimension'];
195         if ($this->dimension == '') {
196             $this->dimension = 0;
197             $args['dimension'] = 0;
198         }
199         if ($args['pagename']) {
200             // Expand relative page names.
201             $page = new WikiPageName($args['pagename'], $basepage);
202             $args['pagename'] = $page->name;
203         }
204         if (empty($args['pagename'])) {
205             return $this->error(_("no page specified"));
206         }
207         $this->pagename = $args['pagename'];
208
209         if (RATING_STORAGE == 'SQL') {
210             $dbi = &$this->_dbi->_backend;
211             extract($dbi->_table_names);
212             if (empty($rating_tbl)) {
213                 $rating_tbl = (!empty($GLOBALS['DBParams']['prefix']) 
214                                ? $GLOBALS['DBParams']['prefix'] : '') . 'rating';
215                 $dbi->_table_names['rating_tbl'] = $rating_tbl;
216             }
217         }
218
219         if ($args['mode'] === 'add') {
220             if (!$user->isSignedIn())
221                 return $this->error(_("You must sign in"));
222             global $Theme;
223             $actionImg = $Theme->_path . $this->actionImgPath();
224             $this->addRating($request->getArg('rating'));
225             ob_end_clean();  // discard any previous output
226             // delete the cache
227             $page = $request->getPage();
228             $page->set('_cached_html', false);
229             $request->cacheControl('MUST-REVALIDATE');
230             $dbi->touch();
231             //fake validators without args
232             $request->appendValidators(array('wikiname' => WIKI_NAME,
233                                              'args'     => hash('')));
234             header('Content-type: image/png');
235             readfile($actionImg);
236             exit();
237         } elseif ($args['mode'] === 'delete') {
238             if (!$user->isSignedIn())
239                 return $this->error(_("You must sign in"));
240             global $Theme;
241             $actionImg = $Theme->_path . $this->actionImgPath();
242             $this->deleteRating();
243             ob_end_clean();  // discard any previous output
244             // delete the cache
245             $page = $request->getPage();
246             $page->set('_cached_html', false);
247             $request->cacheControl('MUST-REVALIDATE');
248             $dbi->touch();
249             //fake validators without args
250             $request->appendValidators(array('wikiname' => WIKI_NAME,
251                                              'args'     => hash('')));
252             header('Content-type: image/png');
253             readfile($actionImg);
254             exit();
255         } elseif (! $args['show'] ) {
256             // we must use the head method instead, because <body> is already printed.
257             // $Theme->addMoreHeaders($this->RatingWidgetJavascript()); 
258             // or we change the header in the ob_buffer.
259
260             //Todo: add a validator based on the users last rating mtime
261             $rating = $this->getRating();
262             /*
263                 static $validated = 0;
264                 if (!$validated) {
265                 //$page = $request->getPage();
266                 //$page->set('_cached_html', false);
267                   $request->cacheControl('REVALIDATE');
268                   $validated = 1;
269                 }
270             */
271             $args['rating'] = $rating;
272             return $this->RatingWidgetHtml($args);
273         } else {
274             if (!$user->isSignedIn())
275                 return $this->error(_("You must sign in"));
276             extract($args);
277             $rating = $this->getRating();
278             $html = HTML::p(sprintf(_("Rated by %d users | Average rating %.1f stars"),
279                                     $this->getNumUsers($this->pagename,$this->dimension),
280                                     $this->getAvg($this->pagename,$this->dimension)),
281                             HTML::br());
282             if ($rating !== false)
283                 $html->pushContent(sprintf(_("Your rating was %.1f"),
284                                            $rating));
285             else {
286                 $pred = $this->getPrediction($this->userid,$this->pagename,$this->dimension);
287                 if (is_string($pred))
288                     $html->pushContent(sprintf(_("%s prediction for you is %s stars"),
289                                                WIKI_NAME, $pred));
290                 elseif ($pred)
291                     $html->pushContent(sprintf(_("%s prediction for you is %.1f stars"),
292                                                WIKI_NAME, $pred));
293             }
294             $html->pushContent(HTML::p());
295             $html->pushContent(HTML::em("(Experimental: This is entirely bogus data)"));
296             return $html;
297         }
298     }
299
300     // box is used to display a fixed-width, narrow version with common header
301     function box($args=false, $request=false, $basepage=false) {
302         if (!$request) $request =& $GLOBALS['request'];
303         if (!$request->_user->isSignedIn()) return;
304         if (!isset($args)) $args = array();
305         $args['small'] = 1;
306         return $this->makeBox(WikiLink(_("RateIt"),'',_("Rate It")),
307                               $this->RatingWidgetHtml($args));
308     }
309
310     function addRating($rating, $userid=null, $pagename=null, $dimension=null) {
311         if (is_null($dimension)) $dimension = $this->dimension;
312         if (is_null($userid))    $userid = $this->userid; 
313         if (is_null($pagename))  $pagename = $this->pagename;
314         if (RATING_STORAGE == 'SQL') {
315             $page = $this->_dbi->getPage($this->pagename);
316             $current = $page->getCurrentRevision();
317             $rateeversion = $current->getVersion();
318             $this->sql_rate($userid, $pagename, $rateeversion, $dimension, $rating);
319         } else {
320             $this->metadata_set_rating($userid, $pagename, $dimension, $rating);
321         }
322     }
323
324     function deleteRating($userid=null, $pagename=null, $dimension=null) {
325         if (is_null($dimension)) $dimension = $this->dimension;
326         if (is_null($userid))    $userid = $this->userid; 
327         if (is_null($pagename))  $pagename = $this->pagename;
328         if (RATING_STORAGE == 'SQL') {
329             $this->sql_delete_rating($userid, $pagename, $dimension);
330         } else {
331             $this->metadata_set_rating($userid, $pagename, $dimension, -1);
332         }
333     }
334
335     function getRating($userid=null, $pagename=null, $dimension=null) {
336         if (is_null($dimension)) $dimension = $this->dimension;
337         if (is_null($userid))    $userid = $this->userid; 
338         if (is_null($pagename))  $pagename = $this->pagename;
339         if (RATING_STORAGE == 'SQL') {
340             $ratings_iter = $this->sql_get_rating($dimension, $userid, $pagename);
341             if ($rating = $ratings_iter->next()) {
342                 return $rating['ratingvalue'];
343             } else 
344                 return false;
345         } else {
346             return $this->metadata_get_rating($userid, $pagename, $dimension);
347         }
348     }
349
350     // TODO
351     // Currently we have to call the "suggest" CGI
352     //   http://www-users.cs.umn.edu/~karypis/suggest/
353     // until we implement a simple recommendation engine.
354     // Note that "suggest" is only free for non-profit organizations.
355     // I am currently writing a binary CGI using suggest, which loads 
356     // data from mysql.
357     function getPrediction($userid=null, $pagename=null, $dimension=null) {
358         if (is_null($dimension)) $dimension = $this->dimension;
359         if (is_null($userid))    $userid   = $this->userid; 
360         if (is_null($pagename))  $pagename = $this->pagename;
361         $dbi = &$this->_dbi->_backend;
362         if (isset($pagename))
363             $page = $dbi->_get_pageid($pagename);
364         else return 0;
365         if (isset($userid))
366             $user = $dbi->_get_pageid($userid);
367         else return 0;
368         
369         return 2.5;
370         
371         if (defined('RATING_EXTERNAL')) {
372             // how call suggest.exe? as CGI or natively
373             //$rating = HTML::Raw("<!--#include virtual=".RATING_ENGINE." -->");
374             $args = "-u$user -p$page -malpha"; // --top 10
375             if (isset($dimension))
376                 $args .= " -d$dimension";
377             $rating = passthru(RATING_EXTERNAL . " $args");
378         } else {
379             $rating = $this->php_prediction($userid, $pagename, $dimension);
380         }
381         return $rating;
382     }
383
384     /**
385      * TODO: slow item-based recommendation engine, similar to suggest RType=2.
386      * Only the SUGGEST_EstimateAlpha part
387      */
388     function php_prediction($userid=null, $pagename=null, $dimension=null) {
389         if (is_null($dimension)) $dimension = $this->dimension;
390         if (is_null($userid))    $userid   = $this->userid; 
391         if (is_null($pagename))  $pagename = $this->pagename;
392         if (RATING_STORAGE == 'SQL') {
393             $rating = 2.5;
394         } else {
395             $rating = 2.5;
396         }
397         return $rating;
398     }
399     
400     function getNumUsers($pagename=null, $dimension=null) {
401         if (is_null($dimension)) $dimension = $this->dimension;
402         if (is_null($pagename))  $pagename = $this->pagename;
403         if (RATING_STORAGE == 'SQL') {
404             $ratings_iter = $this->sql_get_rating($dimension, null, $pagename,
405                                                   null, "ratee");
406             return $ratings_iter->count();
407         } else {
408             $page = $this->_dbi->getPage($pagename);
409             $data = $page->get('rating');
410             if (!empty($data[$dimension]))
411                 return count($data[$dimension]);
412             else 
413                 return 0;
414         }
415     }
416     // TODO: metadata method
417     function getAvg($pagename=null, $dimension=null) {
418         if (is_null($dimension)) $dimension = $this->dimension;
419         if (is_null($pagename))  $pagename = $this->pagename;
420         if (RATING_STORAGE == 'SQL') {
421             $dbi = &$this->_dbi->_backend;
422             $where = "WHERE 1";
423             if (isset($pagename)) {
424                 $raterid = $dbi->_get_pageid($pagename, true);
425                 $where .= " AND raterpage=$raterid";
426             }
427             if (isset($dimension)) {
428                 $where .= " AND dimension=$dimension";
429             }
430             //$dbh = &$this->_dbi;
431             extract($dbi->_table_names);
432             $query = "SELECT AVG(ratingvalue) as avg"
433                    . " FROM $rating_tbl r, $page_tbl p "
434                    . $where. " GROUP BY raterpage";
435             $result = $dbi->_dbh->query($query);
436             $iter = new WikiDB_backend_PearDB_generic_iter($this,$result);
437             $row = $iter->next();
438             return $row['avg'];
439         } else {
440             return 2.5;
441         }
442     }
443
444     /**
445      * Get ratings.
446      *
447      * @param dimension  The rating dimension id.
448      *                   Example: 0
449      *                   [optional]
450      *                   If this is null (or left off), the search for ratings
451      *                   is not restricted by dimension.
452      *
453      * @param rater  The page id of the rater, i.e. page doing the rating.
454      *               This is a Wiki page id, often of a user page.
455      *               Example: "DanFr"
456      *               [optional]
457      *               If this is null (or left off), the search for ratings
458      *               is not restricted by rater.
459      *               TODO: Support an array
460      *
461      * @param ratee  The page id of the ratee, i.e. page being rated.
462      *               Example: "DudeWheresMyCar"
463      *               [optional]
464      *               If this is null (or left off), the search for ratings
465      *               is not restricted by ratee.
466      *               TODO: Support an array
467      *
468      * @param orderby An order-by clause with fields and (optionally) ASC
469      *                or DESC.
470      *               Example: "ratingvalue DESC"
471      *               [optional]
472      *               If this is null (or left off), the search for ratings
473      *               has no guaranteed order
474      *
475      * @param pageinfo The type of page that has its info returned (i.e.,
476      *               'pagename', 'hits', and 'pagedata') in the rows.
477      *               Example: "rater"
478      *               [optional]
479      *               If this is null (or left off), the info returned
480      *               is for the 'ratee' page (i.e., thing being rated).
481      *
482      * @return DB iterator with results 
483      */
484     function sql_get_rating($dimension=null, $rater=null, $ratee=null,
485                             $orderby=null, $pageinfo = "ratee") {
486         if (empty($dimension)) $dimension=null;
487         $result = $this->_sql_get_rating_result($dimension, $rater, $ratee, $orderby, $pageinfo);
488         return new WikiDB_backend_PearDB_generic_iter($this, $result);
489     }
490
491     /**
492      * Like get_rating(), but return a result suitable for WikiDB_PageIterator
493      */
494     function _sql_get_rating_page($dimension=null, $rater=null, $ratee=null,
495                                   $orderby=null, $pageinfo = "ratee") {
496         if (empty($dimension)) $dimension=null;
497         $result = $this->_sql_get_rating_result($dimension, $rater, $ratee, $orderby, $pageinfo);
498         return new WikiDB_backend_PearDB_iter($this, $result);
499     }
500
501     /**
502      * @access private
503      * @return DB iterator with results
504      */
505     function _sql_get_rating_result($dimension=null, $rater=null, $ratee=null,
506                                     $orderby=null, $pageinfo = "ratee") {
507         // pageinfo must be 'rater' or 'ratee'
508         if (($pageinfo != "ratee") && ($pageinfo != "rater"))
509             return;
510
511         $dbi = &$this->_dbi->_backend;
512         //$dbh = &$this->_dbi;
513         extract($dbi->_table_names);
514         $where = "WHERE r." . $pageinfo . "page = p.id";
515         if (isset($dimension)) {
516             $where .= " AND dimension=$dimension";
517         }
518         if (isset($rater)) {
519             $raterid = $dbi->_get_pageid($rater, true);
520             $where .= " AND raterpage=$raterid";
521         }
522         if (isset($ratee)) {
523             $rateeid = $dbi->_get_pageid($ratee, true);
524             $where .= " AND rateepage=$rateeid";
525         }
526         $orderbyStr = "";
527         if (isset($orderby)) {
528             $orderbyStr = " ORDER BY " . $orderby;
529         }
530
531         $query = "SELECT *"
532                . " FROM $rating_tbl r, $page_tbl p "
533                . $where
534                . $orderbyStr;
535
536         $result = $dbi->_dbh->query($query);
537         return $result;
538     }
539
540     /**
541      * Delete a rating.
542      *
543      * @param rater  The page id of the rater, i.e. page doing the rating.
544      *               This is a Wiki page id, often of a user page.
545      * @param ratee  The page id of the ratee, i.e. page being rated.
546      * @param dimension  The rating dimension id.
547      *
548      * @access public
549      *
550      * @return true upon success
551      */
552     function sql_delete_rating($rater, $ratee, $dimension) {
553         //$dbh = &$this->_dbi;
554         $dbi = &$this->_dbi->_backend;
555         extract($dbi->_table_names);
556
557         $dbi->lock();
558         $raterid = $dbi->_get_pageid($rater, true);
559         $rateeid = $dbi->_get_pageid($ratee, true);
560         $where = "WHERE raterpage=$raterid and rateepage=$rateeid";
561         if (isset($dimension)) {
562             $where .= " AND dimension=$dimension";
563         }
564         $dbi->_dbh->query("DELETE FROM $rating_tbl $where");
565         $dbi->unlock();
566         return true;
567     }
568
569     /**
570      * Rate a page.
571      *
572      * @param rater  The page id of the rater, i.e. page doing the rating.
573      *               This is a Wiki page id, often of a user page.
574      * @param ratee  The page id of the ratee, i.e. page being rated.
575      * @param rateeversion  The version of the ratee page.
576      * @param dimension  The rating dimension id.
577      * @param rating The rating value (a float).
578      *
579      * @access public
580      *
581      * @return true upon success
582      */
583     //$this->userid, $this->pagename, $this->dimension, $rating);
584     function sql_rate($rater, $ratee, $rateeversion, $dimension, $rating) {
585         $dbi = &$this->_dbi->_backend;
586         extract($dbi->_table_names);
587         if (empty($rating_tbl))
588             $rating_tbl = (!empty($GLOBALS['DBParams']['prefix']) ? $GLOBALS['DBParams']['prefix'] : '') . 'rating';
589
590         $dbi->lock();
591         $raterid = $dbi->_get_pageid($rater, true);
592         $rateeid = $dbi->_get_pageid($ratee, true);
593         $where = "WHERE raterpage=$raterid AND rateepage=$rateeid";
594         if (isset($dimension)) $where .= " AND dimension=$dimension";
595         $dbi->_dbh->query("DELETE FROM $rating_tbl $where");
596         // NOTE: Leave tstamp off the insert, and MySQL automatically updates it (only if MySQL is used)
597         $dbi->_dbh->query("INSERT INTO $rating_tbl (dimension, raterpage, rateepage, ratingvalue, rateeversion) VALUES ('$dimension', $raterid, $rateeid, '$rating', '$rateeversion')");
598         $dbi->unlock();
599         return true;
600     }
601
602     function metadata_get_rating($userid, $pagename, $dimension) {
603         $page = $this->_dbi->getPage($pagename);
604         $data = $page->get('rating');
605         if (!empty($data[$dimension][$userid]))
606             return (float)$data[$dimension][$userid];
607         else 
608             return false;
609     }
610
611     function metadata_set_rating($userid, $pagename, $dimension, $rating = -1) {
612         $page = $this->_dbi->getPage($pagename);
613         $data = $page->get('rating');
614         if ($rating == -1)
615             unset($data[$dimension][$userid]);
616         else {
617             if (empty($data[$dimension][$userid]))
618                 $data[$dimension] = array($userid => (float)$rating);
619             else
620                 $data[$dimension][$userid] = $rating;
621         }
622         $page->set('rating',$data);
623     }
624
625     /**
626      * HTML widget display
627      *
628      * This needs to be put in the <body> section of the page.
629      *
630      * @param pagename    Name of the page to rate
631      * @param version     Version of the page to rate (may be "" for current)
632      * @param imgPrefix   Prefix of the names of the images that display the rating
633      *                    You can have two widgets for the same page displayed at
634      *                    once iff the imgPrefix-s are different.
635      * @param dimension   Id of the dimension to rate
636      * @param small       Makes a smaller ratings widget if non-false
637      *
638      * Limitations: Currently this can only print the current users ratings.
639      *              And only the widget, but no value (for buddies) also.
640      */
641     function RatingWidgetHtml($args) {
642         global $Theme, $request;
643         extract($args);
644         if (!$request->_user->isSignedIn()) return;
645         $imgPrefix = $pagename . $imgPrefix;
646         $actionImgName = $imgPrefix . 'RateItAction';
647         $dbi =& $GLOBALS['request']->getDbh();
648         $version = $dbi->_backend->get_latest_version($pagename);
649
650         // Protect against 's, though not \r or \n
651         $reImgPrefix     = $this->_javascript_quote_string($imgPrefix);
652         $reActionImgName = $this->_javascript_quote_string($actionImgName);
653         $rePagename      = $this->_javascript_quote_string($pagename);
654         //$dimension = $args['pagename'] . "rat";
655     
656         $html = HTML::span(array("id" => $id));
657         for ($i=0; $i < 2; $i++) {
658             $nk[$i]   = $Theme->_findData("images/RateItNk$i.png");
659             $none[$i] = $Theme->_findData("images/RateItRk$i.png");
660         }
661         if (!$small) {
662             $html->pushContent(Button(_("RateIt"),_("RateIt"),$pagename));
663             $html->pushContent(HTML::raw('&nbsp;'));
664         }
665        
666
667         $user = $request->getUser();
668         $userid = $user->getId();
669         if (!isset($args['rating']))
670             $rating = $this->getRating($userid, $pagename, $dimension);
671         if (!$rating) {
672             $pred = $this->getPrediction($userid,$pagename,$dimension);
673         }
674         for ($i = 1; $i <= 10; $i++) {
675             $a1 = HTML::a(array('href' => 'javascript:click(\'' . $reActionImgName . '\',\'' . $rePagename . '\',\'' . $version . '\',\'' . $reImgPrefix . '\',\'' . $dimension . '\',' . ($i/2) . ')'));
676             $img_attr = array();
677             $img_attr['src'] = $nk[$i%2];
678             if (!$rating and !$pred)
679                 $img_attr['src'] = $none[$i%2];
680             $img_attr['name'] = $imgPrefix . $i;
681             $img_attr['border'] = 0;
682             $a1->pushContent(HTML::img($img_attr));
683             $html->pushContent($a1);
684             //This adds a space between the rating smilies:
685             // if (($i%2) == 0) $html->pushContent(' ');
686         }
687         $html->pushContent(HTML::Raw('&nbsp;'));
688         $a0 = HTML::a(array('href' => 'javascript:click(\'' . $reActionImgName . '\',\'' . $rePagename . '\',\'' . $version . '\',\'' . $reImgPrefix . '\',\'' . $dimension . '\',\'X\')'));
689         if ($rating) {
690             $msg = _("Cancel rating");
691             $a0->pushContent(HTML::img(array('src' => $Theme->getImageUrl("RateItCancel"),
692                                              'name'=> $imgPrefix.'Cancel',
693                                              'alt' => $msg)));
694             $a0->addToolTip($msg);
695             $html->pushContent($a0);
696         } elseif ($pred) {
697             $msg = _("Not seen");
698             $html->pushContent(HTML::img(array('src' => $Theme->getImageUrl("RateItCancelN"),
699                                                'name'=> $imgPrefix.'Cancel',
700                                                'alt' => $msg)));
701             //$a0->addToolTip($msg);
702             //$html->pushContent($a0);
703         }
704         $img_attr = array();
705         $img_attr['src'] = $Theme->_findData("images/RateItAction.png");
706         $img_attr['name'] = $actionImgName;
707         //$img_attr['class'] = 'k' . $i;
708         $img_attr['border'] = 0;
709         $html->pushContent(HTML::img($img_attr));
710         // Display the current rating if there is one
711         if ($rating) 
712             $html->pushContent(JavaScript('displayRating(\'' . $reImgPrefix . '\','.$rating .',0)'));
713         elseif ($pred)
714             $html->pushContent(JavaScript('displayRating(\'' . $reImgPrefix . '\','.$pred .',1)'));
715         else 
716             $html->pushContent(JavaScript('displayRating(\'' . $reImgPrefix . '\',0,0)'));    
717         return $html;
718     }
719
720 };
721
722
723 // $Log: not supported by cvs2svn $
724 // Revision 1.1  2004/03/30 02:38:06  rurban
725 // RateIt support (currently no recommendation engine yet)
726 //
727
728 // For emacs users
729 // Local Variables:
730 // mode: php
731 // tab-width: 8
732 // c-basic-offset: 4
733 // c-hanging-comment-ender-p: nil
734 // indent-tabs-mode: nil
735 // End:
736 ?>