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