From 8ecd83773c68fc8bee7a41a917c594b5c873ac7b Mon Sep 17 00:00:00 2001 From: Srdjan Jankovic Date: Tue, 6 Mar 2012 18:52:25 +1300 Subject: [PATCH] Bug 5668 - star ratings in the OPAC --- C4/Auth.pm | 5 +- C4/Output.pm | 23 +++- C4/Ratings.pm | 161 ++++++++++++++++++++ installer/data/mysql/kohastructure.sql | 16 ++ installer/data/mysql/updatedatabase.pl | 19 +++ .../prog/en/modules/admin/preferences/opac.pref | 6 + koha-tmpl/opac-tmpl/prog/en/css/jquery.rating.css | 12 ++ .../en/lib/jquery/plugins/jquery.rating.pack.js | 11 ++ koha-tmpl/opac-tmpl/prog/en/modules/opac-detail.tt | 49 ++++++- .../opac-tmpl/prog/en/modules/opac-results.tt | 57 +++++++ koha-tmpl/opac-tmpl/prog/images/delete.gif | Bin 0 -> 752 bytes koha-tmpl/opac-tmpl/prog/images/star.gif | Bin 0 -> 815 bytes opac/opac-detail.pl | 24 +++ opac/opac-ratings.pl | 104 +++++++++++++ opac/opac-search.pl | 28 ++++- 15 files changed, 507 insertions(+), 8 deletions(-) create mode 100644 C4/Ratings.pm create mode 100644 koha-tmpl/opac-tmpl/prog/en/css/jquery.rating.css create mode 100644 koha-tmpl/opac-tmpl/prog/en/lib/jquery/plugins/jquery.rating.pack.js create mode 100644 koha-tmpl/opac-tmpl/prog/images/delete.gif create mode 100644 koha-tmpl/opac-tmpl/prog/images/star.gif create mode 100755 opac/opac-ratings.pl diff --git a/C4/Auth.pm b/C4/Auth.pm index ccb2c15..f7e54f8 100644 --- a/C4/Auth.pm +++ b/C4/Auth.pm @@ -487,6 +487,7 @@ sub get_template_and_user { SyndeticsSeries => C4::Context->preference("SyndeticsSeries"), SyndeticsCoverImageSize => C4::Context->preference("SyndeticsCoverImageSize"), OPACLocalCoverImages => C4::Context->preference("OPACLocalCoverImages"), + RatingsEnabled => C4::Context->preference("RatingsEnabled"), ); $template->param(OpacPublic => '1') if ($user || C4::Context->preference("OpacPublic")); @@ -982,9 +983,9 @@ sub checkauth { OpacAuthorities => C4::Context->preference("OpacAuthorities"), OpacBrowser => C4::Context->preference("OpacBrowser"), opacheader => C4::Context->preference("opacheader"), - TagsEnabled => C4::Context->preference("TagsEnabled"), - OPACUserCSS => C4::Context->preference("OPACUserCSS"), opacstylesheet => C4::Context->preference("opacstylesheet"), + TagsEnabled => C4::Context->preference("TagsEnabled"), + OPACUserCSS => C4::Context->preference("OPACUserCSS"), intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"), intranetstylesheet => C4::Context->preference("intranetstylesheet"), diff --git a/C4/Output.pm b/C4/Output.pm index 9087075..cb281b6 100644 --- a/C4/Output.pm +++ b/C4/Output.pm @@ -41,13 +41,16 @@ BEGIN { require Exporter; @ISA = qw(Exporter); @EXPORT_OK = qw(&is_ajax ajax_fail); # More stuff should go here instead - %EXPORT_TAGS = ( all =>[qw(&pagination_bar - &output_with_http_headers &output_html_with_http_headers)], - ajax =>[qw(&output_with_http_headers is_ajax)], + %EXPORT_TAGS = ( all =>[qw(&themelanguage &gettemplate setlanguagecookie &pagination_bar + &output_with_http_headers &output_ajax_with_http_headers &output_html_with_http_headers)], + ajax =>[qw(&output_with_http_headers &output_ajax_with_http_headers is_ajax)], html =>[qw(&output_with_http_headers &output_html_with_http_headers)] ); push @EXPORT, qw( - &output_html_with_http_headers &output_with_http_headers FormatData FormatNumber pagination_bar + &themelanguage &gettemplate setlanguagecookie getlanguagecookie + ); + push @EXPORT, qw( + &output_html_with_http_headers &output_ajax_with_http_headers &output_with_http_headers FormatData FormatNumber pagination_bar ); } @@ -306,6 +309,18 @@ sub output_html_with_http_headers ($$$;$) { output_with_http_headers( $query, $cookie, $data, 'html', $status ); } + +sub output_ajax_with_http_headers ($$) { + my ( $query, $js ) = @_; + print $query->header( + -type => 'text/javascript', + -charset => 'UTF-8', + -Pragma => 'no-cache', + -'Cache-Control' => 'no-cache', + -expires => '-1d', + ), $js; +} + sub is_ajax () { my $x_req = $ENV{HTTP_X_REQUESTED_WITH}; return ( $x_req and $x_req =~ /XMLHttpRequest/i ) ? 1 : 0; diff --git a/C4/Ratings.pm b/C4/Ratings.pm new file mode 100644 index 0000000..6f74b4d --- /dev/null +++ b/C4/Ratings.pm @@ -0,0 +1,161 @@ +package C4::Ratings; + +# Copyright 2010 KohaAloha, NZ +# Parts copyright 2011, Catalyst IT, NZ. +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# Koha is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with Koha; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +=head1 C4::Ratings - the Koha API for dealing with star ratings for biblios + +This provides an interface to the ratings system, in order to allow them +to be manipulated or queried. + +=cut + +use strict; +use warnings; +use Carp; +use Exporter; + +use C4::Debug; +use C4::Context; + +=head1 NAME + +C4::Ratings - Koha functions for dealing with star ratings. + +=head1 SYNOPSIS + + use C4::Ratings; + +=head1 DESCRIPTION + +This modules provides somes functions to deal with star ratings. + +=cut + +use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); + +BEGIN { + $VERSION = 3.00; + @ISA = qw(Exporter); + + @EXPORT = qw( + get_rating add_rating + ); + + # %EXPORT_TAGS = (); +} + +=head1 FUNCTIONS + +=cut + +=head2 get_rating + + get_rating($biblionumber, $borrowernumber) + +This returns the rating for the supplied biblionumber. It will also return +the rating that the supplied user gave to the provided biblio. If a particular +value can't be supplied, '0' is returned for that value. + +=head 3 RETURNS + +A hashref containing: + +=over + +=item total - the total number of ratings +=item avg - the average of the ratings +=item avgint - the integer form of the average +=item value - the user's rating + +=back + +=cut + +my ($total_query_sth, $user_query_sth); +sub get_rating { + my ( $biblionumber, $borrowernumber ) = @_; + my $dbh = C4::Context->dbh; + + my $total_query = " + SELECT AVG(value) AS average,COUNT(value) AS total FROM ratings + WHERE biblionumber = ?"; + $total_query_sth = $total_query_sth || $dbh->prepare($total_query); + + $total_query_sth->execute($biblionumber); + my $total_query_res = $total_query_sth->fetchrow_hashref(); + + my $user_rating = 0; + if ($borrowernumber) { + my $user_query = " + SELECT value from ratings + WHERE biblionumber = ? and borrowernumber = ?"; + $user_query_sth ||= $dbh->prepare($user_query); + + $user_query_sth->execute( $biblionumber, $borrowernumber ); + my $user_query_res = $user_query_sth->fetchrow_hashref(); + $user_rating = $user_query_res->{value} || 0; + } + my ( $avg, $avgint ) = 0; + $avg = $total_query_res->{average} || 0; + $avgint = sprintf( "%.0f", $avg ); + + my %rating_hash; + $rating_hash{total} = $total_query_res->{total} || 0; + $rating_hash{avg} = $avg; + $rating_hash{avgint} = $avgint; + $rating_hash{value} = $user_rating; + return \%rating_hash; +} + +=head2 add_rating + + add_rating($biblionumber, $borrowernumber, $value) + +This adds or updates a rating for a particular user on a biblio. If the value +is 0, then the rating will be deleted. If the value is out of the range of +0-5, nothing will happen. + +=cut + +my ($delete_query_sth, $insert_query_sth); +sub add_rating { + my ( $biblionumber, $borrowernumber, $value ) = @_; + if (!defined($biblionumber) || !defined($borrowernumber) || + $value < 0 || $value > 5) { + # Seen this happen, want to know about it if it happens again. + carp "Invalid input coming in to C4::Ratings::add_rating"; + return; + } + if ($borrowernumber == 0) { + carp "Attempted to add a rating for borrower number 0"; + return; + } + my $dbh = C4::Context->dbh; + my $delete_query = "DELETE FROM ratings WHERE borrowernumber = ? AND biblionumber = ? LIMIT 1"; + my $delete_query_sth ||= $dbh->prepare($delete_query); + $delete_query_sth->execute( $borrowernumber, $biblionumber ); + return if $value == 0; # We don't add a rating for zero + + my $insert_query = "INSERT INTO ratings (borrowernumber,biblionumber,value) + VALUES (?,?,?)"; + $insert_query_sth ||= $dbh->prepare($insert_query); + $insert_query_sth->execute( $borrowernumber, $biblionumber, $value ); +} + +1; diff --git a/installer/data/mysql/kohastructure.sql b/installer/data/mysql/kohastructure.sql index 597a8be..007b98e 100644 --- a/installer/data/mysql/kohastructure.sql +++ b/installer/data/mysql/kohastructure.sql @@ -2705,6 +2705,22 @@ CREATE TABLE `bibliocoverimage` ( CONSTRAINT `bibliocoverimage_fk1` FOREIGN KEY (`biblionumber`) REFERENCES `biblio` (`biblionumber`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- +-- 'Ratings' table. This tracks the star ratings set by borrowers. +-- + +DROP TABLE IF EXISTS `ratings`; +CREATE TABLE `ratings` ( + `borrowernumber` int(11) NOT NULL, -- the borrower this rating is for + `biblionumber` int(11) NOT NULL, -- the biblio it's for + `value` tinyint(1) NOT NULL, -- the rating, from 1-5 + `timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP, + PRIMARY KEY (`borrowernumber`,`biblionumber`), + CONSTRAINT `ratings_ibfk_1` FOREIGN KEY (`borrowernumber`) REFERENCES `borrowers` (`borrowernumber`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `ratings_ibfk_2` FOREIGN KEY (`biblionumber`) REFERENCES `biblio` (`biblionumber`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + + /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; diff --git a/installer/data/mysql/updatedatabase.pl b/installer/data/mysql/updatedatabase.pl index e540874..8b01095 100755 --- a/installer/data/mysql/updatedatabase.pl +++ b/installer/data/mysql/updatedatabase.pl @@ -4734,6 +4734,25 @@ if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) { SetVersion($DBversion); } +$DBversion = '3.07.00.XXX'; +if (C4::Context->preference("Version") < TransformToNum($DBversion)) { + $dbh->do( qq | + CREATE TABLE `ratings` ( + `borrowernumber` int(11) NOT NULL, + `biblionumber` int(11) NOT NULL, + `value` tinyint(1) NOT NULL, + `timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP, + PRIMARY KEY (`borrowernumber`, `biblionumber`), + CONSTRAINT `ratings_ibfk_1` FOREIGN KEY (`borrowernumber`) REFERENCES `borrowers` (`borrowernumber`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `ratings_ibfk_2` FOREIGN KEY (`biblionumber`) REFERENCES `biblio` (`biblionumber`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 | ); + + $dbh->do("INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('RatingsEnabled','','Enabled or disables ratings feature in the OPAC',NULL,'YesNo')"); + + print "Upgrade to $DBversion done (Added 'ratings' table, and 'RatingsEnabled' syspref\n"; + SetVersion ($DBversion); +} + =head1 FUNCTIONS =head2 DropAllForeignKeys($table) diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/opac.pref b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/opac.pref index bca7a71..d36659c 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/opac.pref +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/opac.pref @@ -304,6 +304,12 @@ OPAC: - pref: numSearchRSSResults class: long - search results in the RSS feed. + - + - pref: RatingsEnabled + choices: + yes: Show + no: "Don't show" + - star ratings Policy: - - pref: singleBranchMode diff --git a/koha-tmpl/opac-tmpl/prog/en/css/jquery.rating.css b/koha-tmpl/opac-tmpl/prog/en/css/jquery.rating.css new file mode 100644 index 0000000..f59c0a2 --- /dev/null +++ b/koha-tmpl/opac-tmpl/prog/en/css/jquery.rating.css @@ -0,0 +1,12 @@ +/* jQuery.Rating Plugin CSS - http://www.fyneworks.com/jquery/star-rating/ */ +div.rating-cancel,div.star-rating{float:left;width:17px;height:15px;text-indent:-999em;cursor:pointer;display:block;background:transparent;overflow:hidden} +div.rating-cancel,div.rating-cancel a{background:url(../../images/delete.gif) no-repeat 0 -16px} +div.star-rating,div.star-rating a{background:url(../../images/star.gif) no-repeat 0 0px} +div.rating-cancel a,div.star-rating a{display:block;width:16px;height:100%;background-position:0 0px;border:0} +div.star-rating-on a{background-position:0 -16px!important} +div.star-rating-hover a{background-position:0 -32px} +/* Read Only CSS */ +div.star-rating-readonly a{cursor:default !important} +/* Partial Star CSS */ +div.star-rating{background:transparent!important;overflow:hidden!important} +/* END jQuery.Rating Plugin CSS */ diff --git a/koha-tmpl/opac-tmpl/prog/en/lib/jquery/plugins/jquery.rating.pack.js b/koha-tmpl/opac-tmpl/prog/en/lib/jquery/plugins/jquery.rating.pack.js new file mode 100644 index 0000000..5da9f93 --- /dev/null +++ b/koha-tmpl/opac-tmpl/prog/en/lib/jquery/plugins/jquery.rating.pack.js @@ -0,0 +1,11 @@ +/* + ### jQuery Star Rating Plugin v3.14 - 2012-01-26 ### + * Home: http://www.fyneworks.com/jquery/star-rating/ + * Code: http://code.google.com/p/jquery-star-rating-plugin/ + * + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + ### +*/ +eval(function(p,a,c,k,e,r){e=function(c){return(c35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}(';5(1W.1z)(7($){5($.21.1Y)1X{1u.1Q("1P",M,t)}1O(e){};$.n.3=7(i){5(4.S==0)l 4;5(H Q[0]==\'1s\'){5(4.S>1){8 j=Q;l 4.11(7(){$.n.3.G($(4),j)})};$.n.3[Q[0]].G(4,$.20(Q).1T(1)||[]);l 4};8 i=$.U({},$.n.3.1k,i||{});$.n.3.O++;4.1K(\'.k-3-1j\').p(\'k-3-1j\').11(7(){8 a,9=$(4);8 b=(4.28||\'26-3\').1g(/\\[|\\]/g,\'10\').1g(/^\\10+|\\10+$/g,\'\');8 c=$(4.1N||1u.1B);8 d=c.6(\'3\');5(!d||d.1d!=$.n.3.O)d={B:0,1d:$.n.3.O};8 e=d[b];5(e)a=e.6(\'3\');5(e&&a)a.B++;C{a=$.U({},i||{},($.1c?9.1c():($.1C?9.6():u))||{},{B:0,F:[],v:[]});a.w=d.B++;e=$(\'<23 14="k-3-1I"/>\');9.1M(e);e.p(\'3-18-19-1a\');5(9.T(\'J\')||9.12(\'J\'))a.m=t;5(9.12(\'Y\'))a.Y=t;e.1o(a.D=$(\'\'+a.1e+\'\').1f(7(){$(4).3(\'R\');$(4).p(\'k-3-P\')}).1h(7(){$(4).3(\'x\');$(4).E(\'k-3-P\')}).1i(7(){$(4).3(\'r\')}).6(\'3\',a))};8 f=$(\'\'+4.1l+\'\');e.1o(f);5(4.17)f.T(\'17\',4.17);5(4.1m)f.p(4.1m);5(a.1Z)a.s=2;5(H a.s==\'1n\'&&a.s>0){8 g=($.n.Z?f.Z():0)||a.1p;8 h=(a.B%a.s),W=1D.1E(g/a.s);f.Z(W).1F(\'a\').1G({\'1H-1A\':\'-\'+(h*W)+\'1J\'})};5(a.m)f.p(\'k-3-1q\');C f.p(\'k-3-1L\').1f(7(){$(4).3(\'1r\');$(4).3(\'I\')}).1h(7(){$(4).3(\'x\');$(4).3(\'z\')}).1i(7(){$(4).3(\'r\')});5(4.N)a.o=f;5(4.1R=="A"){5($(4).12(\'1S\'))a.o=f};9.1t();9.1U(7(){$(4).3(\'r\')});f.6(\'3.9\',9.6(\'3.k\',f));a.F[a.F.S]=f[0];a.v[a.v.S]=9[0];a.q=d[b]=e;a.1V=c;9.6(\'3\',a);e.6(\'3\',a);f.6(\'3\',a);c.6(\'3\',d)});$(\'.3-18-19-1a\').3(\'x\').E(\'3-18-19-1a\');l 4};$.U($.n.3,{O:0,I:7(){8 a=4.6(\'3\');5(!a)l 4;5(!a.I)l 4;8 b=$(4).6(\'3.9\')||$(4.V==\'15\'?4:u);5(a.I)a.I.G(b[0],[b.K(),$(\'a\',b.6(\'3.k\'))[0]])},z:7(){8 a=4.6(\'3\');5(!a)l 4;5(!a.z)l 4;8 b=$(4).6(\'3.9\')||$(4.V==\'15\'?4:u);5(a.z)a.z.G(b[0],[b.K(),$(\'a\',b.6(\'3.k\'))[0]])},1r:7(){8 a=4.6(\'3\');5(!a)l 4;5(a.m)l;4.3(\'R\');4.1v().1w().X(\'.q-\'+a.w).p(\'k-3-P\')},R:7(){8 a=4.6(\'3\');5(!a)l 4;5(a.m)l;a.q.22().X(\'.q-\'+a.w).E(\'k-3-1x\').E(\'k-3-P\')},x:7(){8 a=4.6(\'3\');5(!a)l 4;4.3(\'R\');5(a.o){a.o.6(\'3.9\').T(\'N\',\'N\');a.o.1v().1w().X(\'.q-\'+a.w).p(\'k-3-1x\')}C $(a.v).1y(\'N\');a.D[a.m||a.Y?\'1t\':\'24\']();4.25()[a.m?\'p\':\'E\'](\'k-3-1q\')},r:7(a,b){8 c=4.6(\'3\');5(!c)l 4;5(c.m)l;c.o=u;5(H a!=\'y\'){5(H a==\'1n\')l $(c.F[a]).3(\'r\',y,b);5(H a==\'1s\')$.11(c.F,7(){5($(4).6(\'3.9\').K()==a)$(4).3(\'r\',y,b)})}C c.o=4[0].V==\'15\'?4.6(\'3.k\'):(4.27(\'.q-\'+c.w)?4:u);4.6(\'3\',c);4.3(\'x\');8 d=$(c.o?c.o.6(\'3.9\'):u);5((b||b==y)&&c.1b)c.1b.G(d[0],[d.K(),$(\'a\',c.o)[0]])},m:7(a,b){8 c=4.6(\'3\');5(!c)l 4;c.m=a||a==y?t:M;5(b)$(c.v).T("J","J");C $(c.v).1y("J");4.6(\'3\',c);4.3(\'x\')},29:7(){4.3(\'m\',t,t)},2a:7(){4.3(\'m\',M,M)}});$.n.3.1k={D:\'2b 2c\',1e:\'\',s:0,1p:16};$(7(){$(\'9[2d=2e].k\').3()})})(1z);',62,139,'|||rating|this|if|data|function|var|input|||||||||||star|return|readOnly|fn|current|addClass|rater|select|split|true|null|inputs|serial|draw|undefined|blur||count|else|cancel|removeClass|stars|apply|typeof|focus|disabled|val|div|false|checked|calls|hover|arguments|drain|length|attr|extend|tagName|spw|filter|required|width|_|each|hasClass|title|class|INPUT||id|to|be|drawn|callback|metadata|call|cancelValue|mouseover|replace|mouseout|click|applied|options|value|className|number|append|starWidth|readonly|fill|string|hide|document|prevAll|andSelf|on|removeAttr|jQuery|left|body|meta|Math|floor|find|css|margin|control|px|not|live|before|form|catch|BackgroundImageCache|execCommand|nodeName|selected|slice|change|context|window|try|msie|half|makeArray|browser|children|span|show|siblings|unnamed|is|name|disable|enable|Cancel|Rating|type|radio'.split('|'),0,{})) \ No newline at end of file diff --git a/koha-tmpl/opac-tmpl/prog/en/modules/opac-detail.tt b/koha-tmpl/opac-tmpl/prog/en/modules/opac-detail.tt index 7af60a0..193e107 100644 --- a/koha-tmpl/opac-tmpl/prog/en/modules/opac-detail.tt +++ b/koha-tmpl/opac-tmpl/prog/en/modules/opac-detail.tt @@ -1,6 +1,8 @@ [% INCLUDE 'doc-head-open.inc' %][% IF ( LibraryNameTitle ) %][% LibraryNameTitle %][% ELSE %]Koha Online[% END %] Catalog › Details for: [% title |html %][% FOREACH subtitl IN subtitle %], [% subtitl.subfield |html %][% END %] [% INCLUDE 'doc-head-close.inc' %] + + [% IF ( opacuserlogin ) %][% IF ( loggedinusername ) %][% IF ( TagsEnabled ) %]