]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/plugin/RateIt.php
better wording
[SourceForge/phpwiki.git] / lib / plugin / RateIt.php
1 <?php // -*-php-*-
2 rcs_id('$Id: RateIt.php,v 1.3 2004-04-01 06:29:51 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.3 $");
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 rating ")."'+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   if ((pred == 0) && (ratingvalue > 0))
129     document[cancel].src = imgSrc + 'Cancel.png';
130   else
131     document[cancel].src = imgSrc + 'CancelN.png';
132 }
133 function click(actionImg, pagename, version, imgPrefix, dimension, rating) {
134   if (rating == 'X') {
135     deleteRating(actionImg, pagename, dimension);
136     displayRating(imgPrefix, 0, 0);
137   } else {
138     submitRating(actionImg, pagename, version, dimension, rating);
139     displayRating(imgPrefix, rating, 0);
140   }
141 }
142 function submitRating(actionImg, page, version, dimension, rating) {
143   var myRand = Math.round(Math.random()*(1000000));
144   var imgSrc = page + '?version=' + version + '&action=".urlencode(_("RateIt"))."&mode=add&rating=' + rating + '&dimension=' + dimension + '&nopurge=cache&rand=' + myRand;
145   //alert('submitRating(' + page + ', ' + version + ', ' + dimension + ', ' + rating + ') => '+imgSrc);
146   document[actionImg].src= imgSrc;
147 }
148 function deleteRating(actionImg, page, dimension) {
149   var myRand = Math.round(Math.random()*(1000000));
150   var imgSrc = '".$urlprefix."' + page + '?action=".urlencode(_("RateIt"))."&mode=delete&dimension=' + dimension + '&nopurge=cache&rand=' + myRand;
151   //alert('deleteRating(' + page + ', ' + version + ', ' + dimension + ')');
152   document[actionImg].src= imgSrc;
153 }
154 ";
155         return JavaScript($js);
156     }
157
158     function actionImgPath() {
159         global $Theme;
160         return $Theme->_findFile("images/RateItAction.png");
161     }
162
163     /**
164      * Take a string and quote it sufficiently to be passed as a Javascript
165      * string between ''s
166      */
167     function _javascript_quote_string($s) {
168         return str_replace("'", "\'", $s);
169     }
170
171     function getDefaultArguments() {
172         return array( 'pagename'  => '[pagename]',
173                       'version'   => false,
174                       'id'        => 'rateit',
175                       'imgPrefix' => '',
176                       'dimension' => false,
177                       'small'     => false,
178                       'show'      => false,
179                       'mode'      => false,
180                       );
181     }
182
183     function head() { // early side-effects (before body)
184         global $Theme;
185         $Theme->addMoreHeaders($this->RatingWidgetJavascript());
186     }
187
188     // todo: only for signed users
189     // todo: set rating dbi for external rating database
190     function run($dbi, $argstr, $request, $basepage) {
191         global $Theme;
192         $this->_request = & $request;
193         $this->_dbi = & $dbi;
194         $user = & $request->getUser();
195         $this->userid = $user->UserName();
196         $args = $this->getArgs($argstr, $request);
197         $this->dimension = $args['dimension'];
198         if ($this->dimension == '') {
199             $this->dimension = 0;
200             $args['dimension'] = 0;
201         }
202         if ($args['pagename']) {
203             // Expand relative page names.
204             $page = new WikiPageName($args['pagename'], $basepage);
205             $args['pagename'] = $page->name;
206         }
207         if (empty($args['pagename'])) {
208             return $this->error(_("no page specified"));
209         }
210         $this->pagename = $args['pagename'];
211
212         if (RATING_STORAGE == 'SQL') {
213             $dbi = &$this->_dbi->_backend;
214             if (isa($dbi,'WikiDB_backend_PearDB'))
215                 $this->dbtype = "PearDB";
216             else
217                 $this->dbtype = "ADODB";
218             $this->iter_class = "WikiDB_backend_".$this->dbtype."_generic_iter";
219             extract($dbi->_table_names);
220             if (empty($rating_tbl)) {
221                 $rating_tbl = (!empty($GLOBALS['DBParams']['prefix']) 
222                                ? $GLOBALS['DBParams']['prefix'] : '') . 'rating';
223                 $dbi->_table_names['rating_tbl'] = $rating_tbl;
224             }
225         }
226
227         if ($args['mode'] === 'add') {
228             if (!$user->isSignedIn())
229                 return $this->error(_("You must sign in"));
230             global $Theme;
231             $actionImg = $Theme->_path . $this->actionImgPath();
232             $this->addRating($request->getArg('rating'));
233             ob_end_clean();  // discard any previous output
234             // delete the cache
235             $page = $request->getPage();
236             $page->set('_cached_html', false);
237             $request->cacheControl('MUST-REVALIDATE');
238             $dbi->touch();
239             //fake validators without args
240             $request->appendValidators(array('wikiname' => WIKI_NAME,
241                                              'args'     => hash('')));
242             header('Content-type: image/png');
243             readfile($actionImg);
244             exit();
245         } elseif ($args['mode'] === 'delete') {
246             if (!$user->isSignedIn())
247                 return $this->error(_("You must sign in"));
248             global $Theme;
249             $actionImg = $Theme->_path . $this->actionImgPath();
250             $this->deleteRating();
251             ob_end_clean();  // discard any previous output
252             // delete the cache
253             $page = $request->getPage();
254             $page->set('_cached_html', false);
255             $request->cacheControl('MUST-REVALIDATE');
256             $dbi->touch();
257             //fake validators without args
258             $request->appendValidators(array('wikiname' => WIKI_NAME,
259                                              'args'     => hash('')));
260             header('Content-type: image/png');
261             readfile($actionImg);
262             exit();
263         } elseif (! $args['show'] ) {
264             // we must use the head method instead, because <body> is already printed.
265             // $Theme->addMoreHeaders($this->RatingWidgetJavascript()); 
266             // or we change the header in the ob_buffer.
267
268             //Todo: add a validator based on the users last rating mtime
269             $rating = $this->getRating();
270             /*
271                 static $validated = 0;
272                 if (!$validated) {
273                 //$page = $request->getPage();
274                 //$page->set('_cached_html', false);
275                   $request->cacheControl('REVALIDATE');
276                   $validated = 1;
277                 }
278             */
279             $args['rating'] = $rating;
280             return $this->RatingWidgetHtml($args);
281         } else {
282             if (!$user->isSignedIn())
283                 return $this->error(_("You must sign in"));
284             extract($args);
285             $rating = $this->getRating();
286             $html = HTML::p(sprintf(_("Rated by %d users | Average rating %.1f stars"),
287                                     $this->getNumUsers($this->pagename,$this->dimension),
288                                     $this->getAvg($this->pagename,$this->dimension)),
289                             HTML::br());
290             if ($rating !== false)
291                 $html->pushContent(sprintf(_("Your rating was %.1f"),
292                                            $rating));
293             else {
294                 $pred = $this->getPrediction($this->userid,$this->pagename,$this->dimension);
295                 if (is_string($pred))
296                     $html->pushContent(sprintf(_("%s prediction for you is %s stars"),
297                                                WIKI_NAME, $pred));
298                 elseif ($pred)
299                     $html->pushContent(sprintf(_("%s prediction for you is %.1f stars"),
300                                                WIKI_NAME, $pred));
301             }
302             $html->pushContent(HTML::p());
303             $html->pushContent(HTML::em("(Experimental: This is entirely bogus data)"));
304             return $html;
305         }
306     }
307
308     // box is used to display a fixed-width, narrow version with common header
309     function box($args=false, $request=false, $basepage=false) {
310         if (!$request) $request =& $GLOBALS['request'];
311         if (!$request->_user->isSignedIn()) return;
312         if (!isset($args)) $args = array();
313         $args['small'] = 1;
314         $argstr = '';
315         foreach ($args as $key => $value)
316             $argstr .= $key."=".$value;
317         $widget = $this->run($request->_dbi, $argstr, $request, $basepage);
318
319         return $this->makeBox(WikiLink(_("RateIt"),'',_("Rate It")),
320                               $widget);
321     }
322
323     function addRating($rating, $userid=null, $pagename=null, $dimension=null) {
324         if (is_null($dimension)) $dimension = $this->dimension;
325         if (is_null($userid))    $userid = $this->userid; 
326         if (is_null($pagename))  $pagename = $this->pagename;
327         if (RATING_STORAGE == 'SQL') {
328             $page = $this->_dbi->getPage($this->pagename);
329             $current = $page->getCurrentRevision();
330             $rateeversion = $current->getVersion();
331             $this->sql_rate($userid, $pagename, $rateeversion, $dimension, $rating);
332         } else {
333             $this->metadata_set_rating($userid, $pagename, $dimension, $rating);
334         }
335     }
336
337     function deleteRating($userid=null, $pagename=null, $dimension=null) {
338         if (is_null($dimension)) $dimension = $this->dimension;
339         if (is_null($userid))    $userid = $this->userid; 
340         if (is_null($pagename))  $pagename = $this->pagename;
341         if (RATING_STORAGE == 'SQL') {
342             $this->sql_delete_rating($userid, $pagename, $dimension);
343         } else {
344             $this->metadata_set_rating($userid, $pagename, $dimension, -1);
345         }
346     }
347
348     function getRating($userid=null, $pagename=null, $dimension=null) {
349         if (is_null($dimension)) $dimension = $this->dimension;
350         if (is_null($userid))    $userid = $this->userid; 
351         if (is_null($pagename))  $pagename = $this->pagename;
352         if (RATING_STORAGE == 'SQL') {
353             $ratings_iter = $this->sql_get_rating($dimension, $userid, $pagename);
354             if ($rating = $ratings_iter->next()) {
355                 return $rating['ratingvalue'];
356             } else 
357                 return false;
358         } else {
359             return $this->metadata_get_rating($userid, $pagename, $dimension);
360         }
361     }
362
363     // TODO
364     // Currently we have to call the "suggest" CGI
365     //   http://www-users.cs.umn.edu/~karypis/suggest/
366     // until we implement a simple recommendation engine.
367     // Note that "suggest" is only free for non-profit organizations.
368     // I am currently writing a binary CGI using suggest, which loads 
369     // data from mysql.
370     function getPrediction($userid=null, $pagename=null, $dimension=null) {
371         if (is_null($dimension)) $dimension = $this->dimension;
372         if (is_null($userid))    $userid   = $this->userid; 
373         if (is_null($pagename))  $pagename = $this->pagename;
374         $dbi = &$this->_dbi->_backend;
375         if (isset($pagename))
376             $page = $dbi->_get_pageid($pagename);
377         else return 0;
378         if (isset($userid))
379             $user = $dbi->_get_pageid($userid);
380         else return 0;
381         
382         return 2.5;
383         
384         if (defined('RATING_EXTERNAL')) {
385             // how call suggest.exe? as CGI or natively
386             //$rating = HTML::Raw("<!--#include virtual=".RATING_ENGINE." -->");
387             $args = "-u$user -p$page -malpha"; // --top 10
388             if (isset($dimension))
389                 $args .= " -d$dimension";
390             $rating = passthru(RATING_EXTERNAL . " $args");
391         } else {
392             $rating = $this->php_prediction($userid, $pagename, $dimension);
393         }
394         return $rating;
395     }
396
397     /**
398      * TODO: slow item-based recommendation engine, similar to suggest RType=2.
399      * Only the SUGGEST_EstimateAlpha part
400      */
401     function php_prediction($userid=null, $pagename=null, $dimension=null) {
402         if (is_null($dimension)) $dimension = $this->dimension;
403         if (is_null($userid))    $userid   = $this->userid; 
404         if (is_null($pagename))  $pagename = $this->pagename;
405         if (RATING_STORAGE == 'SQL') {
406             $rating = 2.5;
407         } else {
408             $rating = 2.5;
409         }
410         return $rating;
411     }
412     
413     function getNumUsers($pagename=null, $dimension=null) {
414         if (is_null($dimension)) $dimension = $this->dimension;
415         if (is_null($pagename))  $pagename = $this->pagename;
416         if (RATING_STORAGE == 'SQL') {
417             $ratings_iter = $this->sql_get_rating($dimension, null, $pagename,
418                                                   null, "ratee");
419             return $ratings_iter->count();
420         } else {
421             $page = $this->_dbi->getPage($pagename);
422             $data = $page->get('rating');
423             if (!empty($data[$dimension]))
424                 return count($data[$dimension]);
425             else 
426                 return 0;
427         }
428     }
429     // TODO: metadata method
430     function getAvg($pagename=null, $dimension=null) {
431         if (is_null($dimension)) $dimension = $this->dimension;
432         if (is_null($pagename))  $pagename = $this->pagename;
433         if (RATING_STORAGE == 'SQL') {
434             $dbi = &$this->_dbi->_backend;
435             $where = "WHERE 1";
436             if (isset($pagename)) {
437                 $raterid = $dbi->_get_pageid($pagename, true);
438                 $where .= " AND raterpage=$raterid";
439             }
440             if (isset($dimension)) {
441                 $where .= " AND dimension=$dimension";
442             }
443             //$dbh = &$this->_dbi;
444             extract($dbi->_table_names);
445             $query = "SELECT AVG(ratingvalue) as avg"
446                    . " FROM $rating_tbl r, $page_tbl p "
447                    . $where. " GROUP BY raterpage";
448             $result = $dbi->_dbh->query($query);
449             $iter = new $this->iter_class($this,$result);
450             $row = $iter->next();
451             return $row['avg'];
452         } else {
453             return 2.5;
454         }
455     }
456
457     /**
458      * Get ratings.
459      *
460      * @param dimension  The rating dimension id.
461      *                   Example: 0
462      *                   [optional]
463      *                   If this is null (or left off), the search for ratings
464      *                   is not restricted by dimension.
465      *
466      * @param rater  The page id of the rater, i.e. page doing the rating.
467      *               This is a Wiki page id, often of a user page.
468      *               Example: "DanFr"
469      *               [optional]
470      *               If this is null (or left off), the search for ratings
471      *               is not restricted by rater.
472      *               TODO: Support an array
473      *
474      * @param ratee  The page id of the ratee, i.e. page being rated.
475      *               Example: "DudeWheresMyCar"
476      *               [optional]
477      *               If this is null (or left off), the search for ratings
478      *               is not restricted by ratee.
479      *               TODO: Support an array
480      *
481      * @param orderby An order-by clause with fields and (optionally) ASC
482      *                or DESC.
483      *               Example: "ratingvalue DESC"
484      *               [optional]
485      *               If this is null (or left off), the search for ratings
486      *               has no guaranteed order
487      *
488      * @param pageinfo The type of page that has its info returned (i.e.,
489      *               'pagename', 'hits', and 'pagedata') in the rows.
490      *               Example: "rater"
491      *               [optional]
492      *               If this is null (or left off), the info returned
493      *               is for the 'ratee' page (i.e., thing being rated).
494      *
495      * @return DB iterator with results 
496      */
497     function sql_get_rating($dimension=null, $rater=null, $ratee=null,
498                             $orderby=null, $pageinfo = "ratee") {
499         if (empty($dimension)) $dimension=null;
500         $result = $this->_sql_get_rating_result($dimension, $rater, $ratee, $orderby, $pageinfo);
501         return new $this->iter_class($this, $result);
502     }
503
504     /**
505      * Like get_rating(), but return a result suitable for WikiDB_PageIterator
506      */
507     function _sql_get_rating_page($dimension=null, $rater=null, $ratee=null,
508                                   $orderby=null, $pageinfo = "ratee") {
509         if (empty($dimension)) $dimension=null;
510         $result = $this->_sql_get_rating_result($dimension, $rater, $ratee, $orderby, $pageinfo);
511         
512         return new $this->iter_class($this, $result);
513     }
514
515     /**
516      * @access private
517      * @return DB iterator with results
518      */
519     function _sql_get_rating_result($dimension=null, $rater=null, $ratee=null,
520                                     $orderby=null, $pageinfo = "ratee") {
521         // pageinfo must be 'rater' or 'ratee'
522         if (($pageinfo != "ratee") && ($pageinfo != "rater"))
523             return;
524
525         $dbi = &$this->_dbi->_backend;
526         //$dbh = &$this->_dbi;
527         extract($dbi->_table_names);
528         $where = "WHERE r." . $pageinfo . "page = p.id";
529         if (isset($dimension)) {
530             $where .= " AND dimension=$dimension";
531         }
532         if (isset($rater)) {
533             $raterid = $dbi->_get_pageid($rater, true);
534             $where .= " AND raterpage=$raterid";
535         }
536         if (isset($ratee)) {
537             $rateeid = $dbi->_get_pageid($ratee, true);
538             $where .= " AND rateepage=$rateeid";
539         }
540         $orderbyStr = "";
541         if (isset($orderby)) {
542             $orderbyStr = " ORDER BY " . $orderby;
543         }
544
545         $query = "SELECT *"
546                . " FROM $rating_tbl r, $page_tbl p "
547                . $where
548                . $orderbyStr;
549
550         $result = $dbi->_dbh->query($query);
551         return $result;
552     }
553
554     /**
555      * Delete a rating.
556      *
557      * @param rater  The page id of the rater, i.e. page doing the rating.
558      *               This is a Wiki page id, often of a user page.
559      * @param ratee  The page id of the ratee, i.e. page being rated.
560      * @param dimension  The rating dimension id.
561      *
562      * @access public
563      *
564      * @return true upon success
565      */
566     function sql_delete_rating($rater, $ratee, $dimension) {
567         //$dbh = &$this->_dbi;
568         $dbi = &$this->_dbi->_backend;
569         extract($dbi->_table_names);
570
571         $dbi->lock();
572         $raterid = $dbi->_get_pageid($rater, true);
573         $rateeid = $dbi->_get_pageid($ratee, true);
574         $where = "WHERE raterpage=$raterid and rateepage=$rateeid";
575         if (isset($dimension)) {
576             $where .= " AND dimension=$dimension";
577         }
578         $dbi->_dbh->query("DELETE FROM $rating_tbl $where");
579         $dbi->unlock();
580         return true;
581     }
582
583     /**
584      * Rate a page.
585      *
586      * @param rater  The page id of the rater, i.e. page doing the rating.
587      *               This is a Wiki page id, often of a user page.
588      * @param ratee  The page id of the ratee, i.e. page being rated.
589      * @param rateeversion  The version of the ratee page.
590      * @param dimension  The rating dimension id.
591      * @param rating The rating value (a float).
592      *
593      * @access public
594      *
595      * @return true upon success
596      */
597     //$this->userid, $this->pagename, $this->dimension, $rating);
598     function sql_rate($rater, $ratee, $rateeversion, $dimension, $rating) {
599         $dbi = &$this->_dbi->_backend;
600         extract($dbi->_table_names);
601         if (empty($rating_tbl))
602             $rating_tbl = (!empty($GLOBALS['DBParams']['prefix']) ? $GLOBALS['DBParams']['prefix'] : '') . 'rating';
603
604         $dbi->lock();
605         $raterid = $dbi->_get_pageid($rater, true);
606         $rateeid = $dbi->_get_pageid($ratee, true);
607         $where = "WHERE raterpage=$raterid AND rateepage=$rateeid";
608         if (isset($dimension)) $where .= " AND dimension=$dimension";
609         $dbi->_dbh->query("DELETE FROM $rating_tbl $where");
610         // NOTE: Leave tstamp off the insert, and MySQL automatically updates it (only if MySQL is used)
611         $dbi->_dbh->query("INSERT INTO $rating_tbl (dimension, raterpage, rateepage, ratingvalue, rateeversion) VALUES ('$dimension', $raterid, $rateeid, '$rating', '$rateeversion')");
612         $dbi->unlock();
613         return true;
614     }
615
616     function metadata_get_rating($userid, $pagename, $dimension) {
617         $page = $this->_dbi->getPage($pagename);
618         $data = $page->get('rating');
619         if (!empty($data[$dimension][$userid]))
620             return (float)$data[$dimension][$userid];
621         else 
622             return false;
623     }
624
625     function metadata_set_rating($userid, $pagename, $dimension, $rating = -1) {
626         $page = $this->_dbi->getPage($pagename);
627         $data = $page->get('rating');
628         if ($rating == -1)
629             unset($data[$dimension][$userid]);
630         else {
631             if (empty($data[$dimension][$userid]))
632                 $data[$dimension] = array($userid => (float)$rating);
633             else
634                 $data[$dimension][$userid] = $rating;
635         }
636         $page->set('rating',$data);
637     }
638
639     /**
640      * HTML widget display
641      *
642      * This needs to be put in the <body> section of the page.
643      *
644      * @param pagename    Name of the page to rate
645      * @param version     Version of the page to rate (may be "" for current)
646      * @param imgPrefix   Prefix of the names of the images that display the rating
647      *                    You can have two widgets for the same page displayed at
648      *                    once iff the imgPrefix-s are different.
649      * @param dimension   Id of the dimension to rate
650      * @param small       Makes a smaller ratings widget if non-false
651      *
652      * Limitations: Currently this can only print the current users ratings.
653      *              And only the widget, but no value (for buddies) also.
654      */
655     function RatingWidgetHtml($args) {
656         global $Theme, $request;
657         extract($args);
658         if (!$request->_user->isSignedIn()) return;
659         $imgPrefix = $pagename . $imgPrefix;
660         $actionImgName = $imgPrefix . 'RateItAction';
661         $dbi =& $GLOBALS['request']->getDbh();
662         $version = $dbi->_backend->get_latest_version($pagename);
663
664         // Protect against 's, though not \r or \n
665         $reImgPrefix     = $this->_javascript_quote_string($imgPrefix);
666         $reActionImgName = $this->_javascript_quote_string($actionImgName);
667         $rePagename      = $this->_javascript_quote_string($pagename);
668         //$dimension = $args['pagename'] . "rat";
669     
670         $html = HTML::span(array("id" => $id));
671         for ($i=0; $i < 2; $i++) {
672             $nk[$i]   = $Theme->_findData("images/RateItNk$i.png");
673             $none[$i] = $Theme->_findData("images/RateItRk$i.png");
674         }
675         if (!$small) {
676             $html->pushContent(Button(_("RateIt"),_("RateIt"),$pagename));
677             $html->pushContent(HTML::raw('&nbsp;'));
678         }
679        
680
681         $user = $request->getUser();
682         $userid = $user->getId();
683         if (!isset($args['rating']))
684             $rating = $this->getRating($userid, $pagename, $dimension);
685         if (!$rating) {
686             $pred = $this->getPrediction($userid,$pagename,$dimension);
687         }
688         for ($i = 1; $i <= 10; $i++) {
689             $a1 = HTML::a(array('href' => 'javascript:click(\'' . $reActionImgName . '\',\'' . $rePagename . '\',\'' . $version . '\',\'' . $reImgPrefix . '\',\'' . $dimension . '\',' . ($i/2) . ')'));
690             $img_attr = array();
691             $img_attr['src'] = $nk[$i%2];
692             if (!$rating and !$pred)
693                 $img_attr['src'] = $none[$i%2];
694             $img_attr['name'] = $imgPrefix . $i;
695             $img_attr['border'] = 0;
696             $a1->pushContent(HTML::img($img_attr));
697             $a1->addToolTip(_("Rate the topic of this page"));
698             $html->pushContent($a1);
699             //This adds a space between the rating smilies:
700             // if (($i%2) == 0) $html->pushContent(' ');
701         }
702         $html->pushContent(HTML::Raw('&nbsp;'));
703         $a0 = HTML::a(array('href' => 'javascript:click(\'' . $reActionImgName . '\',\'' . $rePagename . '\',\'' . $version . '\',\'' . $reImgPrefix . '\',\'' . $dimension . '\',\'X\')'));
704         if ($rating) {
705             $msg = _("Cancel rating");
706             $a0->pushContent(HTML::img(array('src' => $Theme->getImageUrl("RateItCancel"),
707                                              'name'=> $imgPrefix.'Cancel',
708                                              'alt' => $msg)));
709             $a0->addToolTip($msg);
710             $html->pushContent($a0);
711         } elseif ($pred) {
712             $msg = _("No opinion");
713             $html->pushContent(HTML::img(array('src' => $Theme->getImageUrl("RateItCancelN"),
714                                                'name'=> $imgPrefix.'Cancel',
715                                                'alt' => $msg)));
716             //$a0->addToolTip($msg);
717             //$html->pushContent($a0);
718         }
719         $img_attr = array();
720         $img_attr['src'] = $Theme->_findData("images/RateItAction.png");
721         $img_attr['name'] = $actionImgName;
722         //$img_attr['class'] = 'k' . $i;
723         $img_attr['border'] = 0;
724         $html->pushContent(HTML::img($img_attr));
725         // Display the current rating if there is one
726         if ($rating) 
727             $html->pushContent(JavaScript('displayRating(\'' . $reImgPrefix . '\','.$rating .',0)'));
728         elseif ($pred)
729             $html->pushContent(JavaScript('displayRating(\'' . $reImgPrefix . '\','.$pred .',1)'));
730         else 
731             $html->pushContent(JavaScript('displayRating(\'' . $reImgPrefix . '\',0,0)'));    
732         return $html;
733     }
734
735 };
736
737
738 // $Log: not supported by cvs2svn $
739 // Revision 1.2  2004/03/31 06:22:22  rurban
740 // shorter javascript,
741 // added prediction buttons and display logic,
742 // empty HTML if not signed in.
743 // fixed deleting (empty dimension => 0)
744 //
745 // Revision 1.1  2004/03/30 02:38:06  rurban
746 // RateIt support (currently no recommendation engine yet)
747 //
748
749 // For emacs users
750 // Local Variables:
751 // mode: php
752 // tab-width: 8
753 // c-basic-offset: 4
754 // c-hanging-comment-ender-p: nil
755 // indent-tabs-mode: nil
756 // End:
757 ?>