2 rcs_id('$Id: RateIt.php,v 1.2 2004-03-31 06:22:22 rurban Exp $');
4 Copyright 2004 $ThePhpWikiProgrammingTeam
6 This file is (not yet) part of PhpWiki.
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.
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.
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
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');
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.
33 * There should be two methods to store ratings:
34 * In a SQL database as in wikilens http://dickens.cs.umn.edu/dfrankow/wikilens
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
41 * wikilens plans several user-centered applications like:
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
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.
59 * For a simple rating system one can also store the rating in the page
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 ?>
69 * @author: Dan Frankowski (wikilens author), Reini Urban (as plugin)
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)
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)
89 require_once("lib/WikiPlugin.php");
91 class WikiPlugin_RateIt
97 function getDescription() {
98 return _("Recommendation system. Store user ratings per page");
100 function getVersion() {
101 return preg_replace("/[Revision: $]/", '',
102 "\$Revision: 1.2 $");
105 function RatingWidgetJavascript() {
107 $img = substr($Theme->_findData("images/RateItNk0.png"),0,-7);
108 $urlprefix = WikiUrl("",0,1);
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."';
116 document[imgName].title = '"._("Predicted value ")."'+ratingvalue;
118 document[imgName].title = '"._("Your rating ")."'+ratingvalue;
119 if (i<=(ratingvalue*2)) {
121 document[imgName].src = imgSrc + ((i%2) ? 'Rk1' : 'Rk0') + '.png';
123 document[imgName].src = imgSrc + ((i%2) ? 'Ok1' : 'Ok0') + '.png';
125 document[imgName].src = imgSrc + ((i%2) ? 'Nk1' : 'Nk0') + '.png';
128 document[cancel].src = imgSrc + 'Cancel' + ((ratingvalue) ? '.png' : 'N.png');
130 function click(actionImg, pagename, version, imgPrefix, dimension, rating) {
132 deleteRating(actionImg, pagename, dimension);
133 displayRating(imgPrefix, 0, 0);
135 submitRating(actionImg, pagename, version, dimension, rating);
136 displayRating(imgPrefix, rating, 0);
139 function submitRating(actionImg, page, version, dimension, rating) {
140 var myRand = Math.round(Math.random()*(1000000));
141 var imgSrc = page + '?version=' + version + '&action=".urlencode(_("RateIt"))."&mode=add&rating=' + rating + '&dimension=' + dimension + '&nopurge=cache&rand=' + myRand;
142 //alert('submitRating(' + page + ', ' + version + ', ' + dimension + ', ' + rating + ') => '+imgSrc);
143 document[actionImg].src= imgSrc;
145 function deleteRating(actionImg, page, dimension) {
146 var myRand = Math.round(Math.random()*(1000000));
147 var imgSrc = '".$urlprefix."' + page + '?action=".urlencode(_("RateIt"))."&mode=delete&dimension=' + dimension + '&nopurge=cache&rand=' + myRand;
148 //alert('deleteRating(' + page + ', ' + version + ', ' + dimension + ')');
149 document[actionImg].src= imgSrc;
152 return JavaScript($js);
155 function actionImgPath() {
157 return $Theme->_findFile("images/RateItAction.png");
161 * Take a string and quote it sufficiently to be passed as a Javascript
164 function _javascript_quote_string($s) {
165 return str_replace("'", "\'", $s);
168 function getDefaultArguments() {
169 return array( 'pagename' => '[pagename]',
173 'dimension' => false,
180 function head() { // early side-effects (before body)
182 $Theme->addMoreHeaders($this->RatingWidgetJavascript());
185 // todo: only for signed users
186 // todo: set rating dbi for external rating database
187 function run($dbi, $argstr, $request, $basepage) {
189 $this->_request = & $request;
190 $this->_dbi = & $dbi;
191 $user = & $request->getUser();
192 $this->userid = $user->UserName();
193 $args = $this->getArgs($argstr, $request);
194 $this->dimension = $args['dimension'];
195 if ($this->dimension == '') {
196 $this->dimension = 0;
197 $args['dimension'] = 0;
199 if ($args['pagename']) {
200 // Expand relative page names.
201 $page = new WikiPageName($args['pagename'], $basepage);
202 $args['pagename'] = $page->name;
204 if (empty($args['pagename'])) {
205 return $this->error(_("no page specified"));
207 $this->pagename = $args['pagename'];
209 if (RATING_STORAGE == 'SQL') {
210 $dbi = &$this->_dbi->_backend;
211 extract($dbi->_table_names);
212 if (empty($rating_tbl)) {
213 $rating_tbl = (!empty($GLOBALS['DBParams']['prefix'])
214 ? $GLOBALS['DBParams']['prefix'] : '') . 'rating';
215 $dbi->_table_names['rating_tbl'] = $rating_tbl;
219 if ($args['mode'] === 'add') {
220 if (!$user->isSignedIn())
221 return $this->error(_("You must sign in"));
223 $actionImg = $Theme->_path . $this->actionImgPath();
224 $this->addRating($request->getArg('rating'));
225 ob_end_clean(); // discard any previous output
227 $page = $request->getPage();
228 $page->set('_cached_html', false);
229 $request->cacheControl('MUST-REVALIDATE');
231 //fake validators without args
232 $request->appendValidators(array('wikiname' => WIKI_NAME,
233 'args' => hash('')));
234 header('Content-type: image/png');
235 readfile($actionImg);
237 } elseif ($args['mode'] === 'delete') {
238 if (!$user->isSignedIn())
239 return $this->error(_("You must sign in"));
241 $actionImg = $Theme->_path . $this->actionImgPath();
242 $this->deleteRating();
243 ob_end_clean(); // discard any previous output
245 $page = $request->getPage();
246 $page->set('_cached_html', false);
247 $request->cacheControl('MUST-REVALIDATE');
249 //fake validators without args
250 $request->appendValidators(array('wikiname' => WIKI_NAME,
251 'args' => hash('')));
252 header('Content-type: image/png');
253 readfile($actionImg);
255 } elseif (! $args['show'] ) {
256 // we must use the head method instead, because <body> is already printed.
257 // $Theme->addMoreHeaders($this->RatingWidgetJavascript());
258 // or we change the header in the ob_buffer.
260 //Todo: add a validator based on the users last rating mtime
261 $rating = $this->getRating();
263 static $validated = 0;
265 //$page = $request->getPage();
266 //$page->set('_cached_html', false);
267 $request->cacheControl('REVALIDATE');
271 $args['rating'] = $rating;
272 return $this->RatingWidgetHtml($args);
274 if (!$user->isSignedIn())
275 return $this->error(_("You must sign in"));
277 $rating = $this->getRating();
278 $html = HTML::p(sprintf(_("Rated by %d users | Average rating %.1f stars"),
279 $this->getNumUsers($this->pagename,$this->dimension),
280 $this->getAvg($this->pagename,$this->dimension)),
282 if ($rating !== false)
283 $html->pushContent(sprintf(_("Your rating was %.1f"),
286 $pred = $this->getPrediction($this->userid,$this->pagename,$this->dimension);
287 if (is_string($pred))
288 $html->pushContent(sprintf(_("%s prediction for you is %s stars"),
291 $html->pushContent(sprintf(_("%s prediction for you is %.1f stars"),
294 $html->pushContent(HTML::p());
295 $html->pushContent(HTML::em("(Experimental: This is entirely bogus data)"));
300 // box is used to display a fixed-width, narrow version with common header
301 function box($args=false, $request=false, $basepage=false) {
302 if (!$request) $request =& $GLOBALS['request'];
303 if (!$request->_user->isSignedIn()) return;
304 if (!isset($args)) $args = array();
306 return $this->makeBox(WikiLink(_("RateIt"),'',_("Rate It")),
307 $this->RatingWidgetHtml($args));
310 function addRating($rating, $userid=null, $pagename=null, $dimension=null) {
311 if (is_null($dimension)) $dimension = $this->dimension;
312 if (is_null($userid)) $userid = $this->userid;
313 if (is_null($pagename)) $pagename = $this->pagename;
314 if (RATING_STORAGE == 'SQL') {
315 $page = $this->_dbi->getPage($this->pagename);
316 $current = $page->getCurrentRevision();
317 $rateeversion = $current->getVersion();
318 $this->sql_rate($userid, $pagename, $rateeversion, $dimension, $rating);
320 $this->metadata_set_rating($userid, $pagename, $dimension, $rating);
324 function deleteRating($userid=null, $pagename=null, $dimension=null) {
325 if (is_null($dimension)) $dimension = $this->dimension;
326 if (is_null($userid)) $userid = $this->userid;
327 if (is_null($pagename)) $pagename = $this->pagename;
328 if (RATING_STORAGE == 'SQL') {
329 $this->sql_delete_rating($userid, $pagename, $dimension);
331 $this->metadata_set_rating($userid, $pagename, $dimension, -1);
335 function getRating($userid=null, $pagename=null, $dimension=null) {
336 if (is_null($dimension)) $dimension = $this->dimension;
337 if (is_null($userid)) $userid = $this->userid;
338 if (is_null($pagename)) $pagename = $this->pagename;
339 if (RATING_STORAGE == 'SQL') {
340 $ratings_iter = $this->sql_get_rating($dimension, $userid, $pagename);
341 if ($rating = $ratings_iter->next()) {
342 return $rating['ratingvalue'];
346 return $this->metadata_get_rating($userid, $pagename, $dimension);
351 // Currently we have to call the "suggest" CGI
352 // http://www-users.cs.umn.edu/~karypis/suggest/
353 // until we implement a simple recommendation engine.
354 // Note that "suggest" is only free for non-profit organizations.
355 // I am currently writing a binary CGI using suggest, which loads
357 function getPrediction($userid=null, $pagename=null, $dimension=null) {
358 if (is_null($dimension)) $dimension = $this->dimension;
359 if (is_null($userid)) $userid = $this->userid;
360 if (is_null($pagename)) $pagename = $this->pagename;
361 $dbi = &$this->_dbi->_backend;
362 if (isset($pagename))
363 $page = $dbi->_get_pageid($pagename);
366 $user = $dbi->_get_pageid($userid);
371 if (defined('RATING_EXTERNAL')) {
372 // how call suggest.exe? as CGI or natively
373 //$rating = HTML::Raw("<!--#include virtual=".RATING_ENGINE." -->");
374 $args = "-u$user -p$page -malpha"; // --top 10
375 if (isset($dimension))
376 $args .= " -d$dimension";
377 $rating = passthru(RATING_EXTERNAL . " $args");
379 $rating = $this->php_prediction($userid, $pagename, $dimension);
385 * TODO: slow item-based recommendation engine, similar to suggest RType=2.
386 * Only the SUGGEST_EstimateAlpha part
388 function php_prediction($userid=null, $pagename=null, $dimension=null) {
389 if (is_null($dimension)) $dimension = $this->dimension;
390 if (is_null($userid)) $userid = $this->userid;
391 if (is_null($pagename)) $pagename = $this->pagename;
392 if (RATING_STORAGE == 'SQL') {
400 function getNumUsers($pagename=null, $dimension=null) {
401 if (is_null($dimension)) $dimension = $this->dimension;
402 if (is_null($pagename)) $pagename = $this->pagename;
403 if (RATING_STORAGE == 'SQL') {
404 $ratings_iter = $this->sql_get_rating($dimension, null, $pagename,
406 return $ratings_iter->count();
408 $page = $this->_dbi->getPage($pagename);
409 $data = $page->get('rating');
410 if (!empty($data[$dimension]))
411 return count($data[$dimension]);
416 // TODO: metadata method
417 function getAvg($pagename=null, $dimension=null) {
418 if (is_null($dimension)) $dimension = $this->dimension;
419 if (is_null($pagename)) $pagename = $this->pagename;
420 if (RATING_STORAGE == 'SQL') {
421 $dbi = &$this->_dbi->_backend;
423 if (isset($pagename)) {
424 $raterid = $dbi->_get_pageid($pagename, true);
425 $where .= " AND raterpage=$raterid";
427 if (isset($dimension)) {
428 $where .= " AND dimension=$dimension";
430 //$dbh = &$this->_dbi;
431 extract($dbi->_table_names);
432 $query = "SELECT AVG(ratingvalue) as avg"
433 . " FROM $rating_tbl r, $page_tbl p "
434 . $where. " GROUP BY raterpage";
435 $result = $dbi->_dbh->query($query);
436 $iter = new WikiDB_backend_PearDB_generic_iter($this,$result);
437 $row = $iter->next();
447 * @param dimension The rating dimension id.
450 * If this is null (or left off), the search for ratings
451 * is not restricted by dimension.
453 * @param rater The page id of the rater, i.e. page doing the rating.
454 * This is a Wiki page id, often of a user page.
457 * If this is null (or left off), the search for ratings
458 * is not restricted by rater.
459 * TODO: Support an array
461 * @param ratee The page id of the ratee, i.e. page being rated.
462 * Example: "DudeWheresMyCar"
464 * If this is null (or left off), the search for ratings
465 * is not restricted by ratee.
466 * TODO: Support an array
468 * @param orderby An order-by clause with fields and (optionally) ASC
470 * Example: "ratingvalue DESC"
472 * If this is null (or left off), the search for ratings
473 * has no guaranteed order
475 * @param pageinfo The type of page that has its info returned (i.e.,
476 * 'pagename', 'hits', and 'pagedata') in the rows.
479 * If this is null (or left off), the info returned
480 * is for the 'ratee' page (i.e., thing being rated).
482 * @return DB iterator with results
484 function sql_get_rating($dimension=null, $rater=null, $ratee=null,
485 $orderby=null, $pageinfo = "ratee") {
486 if (empty($dimension)) $dimension=null;
487 $result = $this->_sql_get_rating_result($dimension, $rater, $ratee, $orderby, $pageinfo);
488 return new WikiDB_backend_PearDB_generic_iter($this, $result);
492 * Like get_rating(), but return a result suitable for WikiDB_PageIterator
494 function _sql_get_rating_page($dimension=null, $rater=null, $ratee=null,
495 $orderby=null, $pageinfo = "ratee") {
496 if (empty($dimension)) $dimension=null;
497 $result = $this->_sql_get_rating_result($dimension, $rater, $ratee, $orderby, $pageinfo);
498 return new WikiDB_backend_PearDB_iter($this, $result);
503 * @return DB iterator with results
505 function _sql_get_rating_result($dimension=null, $rater=null, $ratee=null,
506 $orderby=null, $pageinfo = "ratee") {
507 // pageinfo must be 'rater' or 'ratee'
508 if (($pageinfo != "ratee") && ($pageinfo != "rater"))
511 $dbi = &$this->_dbi->_backend;
512 //$dbh = &$this->_dbi;
513 extract($dbi->_table_names);
514 $where = "WHERE r." . $pageinfo . "page = p.id";
515 if (isset($dimension)) {
516 $where .= " AND dimension=$dimension";
519 $raterid = $dbi->_get_pageid($rater, true);
520 $where .= " AND raterpage=$raterid";
523 $rateeid = $dbi->_get_pageid($ratee, true);
524 $where .= " AND rateepage=$rateeid";
527 if (isset($orderby)) {
528 $orderbyStr = " ORDER BY " . $orderby;
532 . " FROM $rating_tbl r, $page_tbl p "
536 $result = $dbi->_dbh->query($query);
543 * @param rater The page id of the rater, i.e. page doing the rating.
544 * This is a Wiki page id, often of a user page.
545 * @param ratee The page id of the ratee, i.e. page being rated.
546 * @param dimension The rating dimension id.
550 * @return true upon success
552 function sql_delete_rating($rater, $ratee, $dimension) {
553 //$dbh = &$this->_dbi;
554 $dbi = &$this->_dbi->_backend;
555 extract($dbi->_table_names);
558 $raterid = $dbi->_get_pageid($rater, true);
559 $rateeid = $dbi->_get_pageid($ratee, true);
560 $where = "WHERE raterpage=$raterid and rateepage=$rateeid";
561 if (isset($dimension)) {
562 $where .= " AND dimension=$dimension";
564 $dbi->_dbh->query("DELETE FROM $rating_tbl $where");
572 * @param rater The page id of the rater, i.e. page doing the rating.
573 * This is a Wiki page id, often of a user page.
574 * @param ratee The page id of the ratee, i.e. page being rated.
575 * @param rateeversion The version of the ratee page.
576 * @param dimension The rating dimension id.
577 * @param rating The rating value (a float).
581 * @return true upon success
583 //$this->userid, $this->pagename, $this->dimension, $rating);
584 function sql_rate($rater, $ratee, $rateeversion, $dimension, $rating) {
585 $dbi = &$this->_dbi->_backend;
586 extract($dbi->_table_names);
587 if (empty($rating_tbl))
588 $rating_tbl = (!empty($GLOBALS['DBParams']['prefix']) ? $GLOBALS['DBParams']['prefix'] : '') . 'rating';
591 $raterid = $dbi->_get_pageid($rater, true);
592 $rateeid = $dbi->_get_pageid($ratee, true);
593 $where = "WHERE raterpage=$raterid AND rateepage=$rateeid";
594 if (isset($dimension)) $where .= " AND dimension=$dimension";
595 $dbi->_dbh->query("DELETE FROM $rating_tbl $where");
596 // NOTE: Leave tstamp off the insert, and MySQL automatically updates it (only if MySQL is used)
597 $dbi->_dbh->query("INSERT INTO $rating_tbl (dimension, raterpage, rateepage, ratingvalue, rateeversion) VALUES ('$dimension', $raterid, $rateeid, '$rating', '$rateeversion')");
602 function metadata_get_rating($userid, $pagename, $dimension) {
603 $page = $this->_dbi->getPage($pagename);
604 $data = $page->get('rating');
605 if (!empty($data[$dimension][$userid]))
606 return (float)$data[$dimension][$userid];
611 function metadata_set_rating($userid, $pagename, $dimension, $rating = -1) {
612 $page = $this->_dbi->getPage($pagename);
613 $data = $page->get('rating');
615 unset($data[$dimension][$userid]);
617 if (empty($data[$dimension][$userid]))
618 $data[$dimension] = array($userid => (float)$rating);
620 $data[$dimension][$userid] = $rating;
622 $page->set('rating',$data);
626 * HTML widget display
628 * This needs to be put in the <body> section of the page.
630 * @param pagename Name of the page to rate
631 * @param version Version of the page to rate (may be "" for current)
632 * @param imgPrefix Prefix of the names of the images that display the rating
633 * You can have two widgets for the same page displayed at
634 * once iff the imgPrefix-s are different.
635 * @param dimension Id of the dimension to rate
636 * @param small Makes a smaller ratings widget if non-false
638 * Limitations: Currently this can only print the current users ratings.
639 * And only the widget, but no value (for buddies) also.
641 function RatingWidgetHtml($args) {
642 global $Theme, $request;
644 if (!$request->_user->isSignedIn()) return;
645 $imgPrefix = $pagename . $imgPrefix;
646 $actionImgName = $imgPrefix . 'RateItAction';
647 $dbi =& $GLOBALS['request']->getDbh();
648 $version = $dbi->_backend->get_latest_version($pagename);
650 // Protect against 's, though not \r or \n
651 $reImgPrefix = $this->_javascript_quote_string($imgPrefix);
652 $reActionImgName = $this->_javascript_quote_string($actionImgName);
653 $rePagename = $this->_javascript_quote_string($pagename);
654 //$dimension = $args['pagename'] . "rat";
656 $html = HTML::span(array("id" => $id));
657 for ($i=0; $i < 2; $i++) {
658 $nk[$i] = $Theme->_findData("images/RateItNk$i.png");
659 $none[$i] = $Theme->_findData("images/RateItRk$i.png");
662 $html->pushContent(Button(_("RateIt"),_("RateIt"),$pagename));
663 $html->pushContent(HTML::raw(' '));
667 $user = $request->getUser();
668 $userid = $user->getId();
669 if (!isset($args['rating']))
670 $rating = $this->getRating($userid, $pagename, $dimension);
672 $pred = $this->getPrediction($userid,$pagename,$dimension);
674 for ($i = 1; $i <= 10; $i++) {
675 $a1 = HTML::a(array('href' => 'javascript:click(\'' . $reActionImgName . '\',\'' . $rePagename . '\',\'' . $version . '\',\'' . $reImgPrefix . '\',\'' . $dimension . '\',' . ($i/2) . ')'));
677 $img_attr['src'] = $nk[$i%2];
678 if (!$rating and !$pred)
679 $img_attr['src'] = $none[$i%2];
680 $img_attr['name'] = $imgPrefix . $i;
681 $img_attr['border'] = 0;
682 $a1->pushContent(HTML::img($img_attr));
683 $html->pushContent($a1);
684 //This adds a space between the rating smilies:
685 // if (($i%2) == 0) $html->pushContent(' ');
687 $html->pushContent(HTML::Raw(' '));
688 $a0 = HTML::a(array('href' => 'javascript:click(\'' . $reActionImgName . '\',\'' . $rePagename . '\',\'' . $version . '\',\'' . $reImgPrefix . '\',\'' . $dimension . '\',\'X\')'));
690 $msg = _("Cancel rating");
691 $a0->pushContent(HTML::img(array('src' => $Theme->getImageUrl("RateItCancel"),
692 'name'=> $imgPrefix.'Cancel',
694 $a0->addToolTip($msg);
695 $html->pushContent($a0);
697 $msg = _("Not seen");
698 $html->pushContent(HTML::img(array('src' => $Theme->getImageUrl("RateItCancelN"),
699 'name'=> $imgPrefix.'Cancel',
701 //$a0->addToolTip($msg);
702 //$html->pushContent($a0);
705 $img_attr['src'] = $Theme->_findData("images/RateItAction.png");
706 $img_attr['name'] = $actionImgName;
707 //$img_attr['class'] = 'k' . $i;
708 $img_attr['border'] = 0;
709 $html->pushContent(HTML::img($img_attr));
710 // Display the current rating if there is one
712 $html->pushContent(JavaScript('displayRating(\'' . $reImgPrefix . '\','.$rating .',0)'));
714 $html->pushContent(JavaScript('displayRating(\'' . $reImgPrefix . '\','.$pred .',1)'));
716 $html->pushContent(JavaScript('displayRating(\'' . $reImgPrefix . '\',0,0)'));
723 // $Log: not supported by cvs2svn $
724 // Revision 1.1 2004/03/30 02:38:06 rurban
725 // RateIt support (currently no recommendation engine yet)
733 // c-hanging-comment-ender-p: nil
734 // indent-tabs-mode: nil