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