Bug 5668 - Star ratings in the opac
authorMason James <mtj@kohaaloha.com>
Fri, 30 Mar 2012 03:35:29 +0000 (16:35 +1300)
committerPaul Poulain <paul.poulain@biblibre.com>
Tue, 10 Apr 2012 12:40:49 +0000 (14:40 +0200)
patch applied to commit eb3dc448d2378e01a524d082fc6389484b0cc7d8

Signed-off-by: Nicole C. Engard <nengard@bywatersolutions.com>
Turned on star ratings in the opac on details and results
Searched for titles - saw the stars
Clicked on a title
Clicked on the stars
Clicked on the stars to change my rating
Logged out
Tried to click on stars
Logged in as different user
Rated items that were rated already and saw average change
Changed preference to show only on detail and repeated tests
Changed preference to now show stars

All above tests passed. Signing off.

Rebased 3-19-12 by Ian Walls

Signed-off-by: Paul Poulain <paul.poulain@biblibre.com>
19 files changed:
C4/Auth.pm
C4/Output.pm
C4/Ratings.pm [new file with mode: 0644]
installer/data/mysql/kohastructure.sql
installer/data/mysql/sysprefs.sql
installer/data/mysql/updatedatabase.pl
koha-tmpl/intranet-tmpl/prog/en/modules/about.tt
koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/opac.pref
koha-tmpl/opac-tmpl/prog/en/css/jquery.rating.css [new file with mode: 0644]
koha-tmpl/opac-tmpl/prog/en/lib/jquery/plugins/jquery.rating.js [new file with mode: 0644]
koha-tmpl/opac-tmpl/prog/en/modules/opac-detail.tt
koha-tmpl/opac-tmpl/prog/en/modules/opac-results.tt
koha-tmpl/opac-tmpl/prog/images/delete.gif [new file with mode: 0755]
koha-tmpl/opac-tmpl/prog/images/star.gif [new file with mode: 0644]
opac/opac-detail.pl
opac/opac-ratings-ajax.pl [new file with mode: 0755]
opac/opac-ratings.pl [new file with mode: 0755]
opac/opac-search.pl
t/db_dependent/Ratings.t [new file with mode: 0755]

index 0d5ddba..df97408 100644 (file)
@@ -41,8 +41,7 @@ BEGIN {
        if ( psgi_env ) { die 'psgi:exit' }
        else { exit }
     }
-
-    $VERSION     = 3.02;                                                                                                            # set version for version checking
+    $VERSION     = 3.02;    # set version for version checking
     $debug       = $ENV{DEBUG};
     @ISA         = qw(Exporter);
     @EXPORT      = qw(&checkauth &get_template_and_user &haspermission &get_user_subpermissions);
@@ -1665,6 +1664,7 @@ sub getborrowernumber {
     return 0;
 }
 
+
 END { }    # module clean-up code here (global destructor)
 1;
 __END__
index 9087075..8d12552 100644 (file)
@@ -39,18 +39,22 @@ BEGIN {
     # set the version for version checking
     $VERSION = 3.03;
     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)],
-                                       html =>[qw(&output_with_http_headers &output_html_with_http_headers)]
-                               );
+
+ @ISA    = qw(Exporter);
+    @EXPORT_OK = qw(&is_ajax ajax_fail); # More stuff should go here instead
+    %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 pagination_bar
+    );
+    push @EXPORT, qw(
+        &output_html_with_http_headers &output_ajax_with_http_headers &output_with_http_headers FormatData FormatNumber
     );
-}
 
+}
 
 =head1 NAME
 
@@ -306,7 +310,19 @@ sub output_html_with_http_headers ($$$;$) {
     output_with_http_headers( $query, $cookie, $data, 'html', $status );
 }
 
-sub is_ajax () {
+
+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 (file)
index 0000000..43870f3
--- /dev/null
@@ -0,0 +1,249 @@
+package C4::Ratings;
+
+# Copyright 2011 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.
+
+use strict;
+use warnings;
+use Carp;
+use Exporter;
+use POSIX;
+use C4::Debug;
+use C4::Context;
+
+use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
+
+BEGIN {
+    $VERSION = 3.00;
+    @ISA     = qw(Exporter);
+
+    @EXPORT = qw(
+      &GetRating
+      &AddRating
+      &ModRating
+      &DelRating
+    );
+}
+
+=head1 NAME
+
+C4::Ratings - a module to manage user ratings of Koha biblios
+
+=head1 DESCRIPTION
+
+Ratings.pm provides simple functionality for a user to 'rate' a biblio, and to retrieve a biblio's rating info
+
+the 4 subroutines allow a user to add, delete modify and retrieve rating info for a biblio.
+
+The rating can be from 1 to 5 stars, (5 stars being the highest rating)
+
+=head1 SYNOPSIS
+
+Get a rating for a bib
+ my $rating_hashref = GetRating( $biblionumber, undef );
+ my $rating_hashref = GetRating( $biblionumber, $borrowernumber );
+
+Add a rating for a bib
+ my $rating_hashref = AddRating( $biblionumber, $borrowernumber, $rating_value );
+
+Mod a rating for a bib
+ my $rating_hashref = ModRating( $biblionumber, $borrowernumber, $rating_value );
+
+Delete a rating for a bib
+ my $rating_hashref = DelRating( $biblionumber, $borrowernumber );
+
+
+All subroutines in Ratings.pm return a hashref which contain 4 keys
+
+for example, after executing this statment below...
+
+    my $rating_hashref = GetRating ( $biblionumber, $borrowernumber ) ;
+
+$rating_hashref now contains a hashref that looks like this...
+
+    $rating  = {
+             rating_avg       => '2',
+             rating_avg_int   => '2.3',
+             rating_total     => '432',
+             rating_value => '5'
+    }
+
+they 4 keys returned in the hashref are...
+
+    rating_avg:            average rating of a biblio
+    rating_avg_int:        average rating of a biblio, rounded to 1dp
+    rating_total:          total number of ratings of a biblio
+    rating_value:          logged-in user's rating of a biblio
+
+=head1 BUGS
+
+Please use bugs.koha-community.org for tracking bugs.
+
+=head1 SOURCE AVAILABILITY
+
+The source is available from the koha-community.org git server
+L<http://git.koha-community.org>
+
+=head1 AUTHOR
+
+Original code: Mason James <mtj@kohaaloha.com>
+
+=head1 COPYRIGHT
+
+Copyright (c) 2011 Mason James <mtj@kohaaloha.com>
+
+=head1 LICENSE
+
+C4::Ratings is free software. You can redistribute it and/or
+modify it under the same terms as Koha itself.
+
+=head1 CREDITS
+
+ Mason James <mtj@kohaaloha.com>
+ Koha Dev Team <http://koha-community.org>
+
+
+=head2 GetRating
+
+    GetRating($biblionumber, [$borrowernumber])
+
+Get a rating for a bib
+ my $rating_hashref = GetRating( $biblionumber, undef );
+ my $rating_hashref = GetRating( $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.
+
+=head3 RETURNS
+
+A hashref containing:
+
+=over
+
+=item * rating_avg - average rating of a biblio
+=item * rating_avg_int - average rating of a biblio, rounded to 1dp
+=item * rating_total - total number of ratings of a biblio
+=item * rating_value - logged-in user's rating of a biblio
+
+=back
+
+=cut
+
+sub GetRating {
+    my ( $biblionumber, $borrowernumber ) = @_;
+    my $query = qq| SELECT COUNT(*) AS total, SUM(rating_value) AS sum
+FROM ratings WHERE biblionumber = ? |;
+
+    my $sth = C4::Context->dbh->prepare($query);
+    $sth->execute($biblionumber);
+    my $res = $sth->fetchrow_hashref();
+
+    my ( $avg, $avg_int ) = 0;
+
+    if ( $res->{sum} and $res->{total} ) {
+        eval { $avg = $res->{sum} / $res->{total} };
+    }
+
+    $avg_int = sprintf( "%.1f", $avg );
+    $avg     = sprintf( "%.0f", $avg );
+
+    my %rating_hash;
+    $rating_hash{rating_total}   = $res->{total} || 0;
+    $rating_hash{rating_avg}     = $avg || 0;
+    $rating_hash{rating_avg_int} = $avg_int ||0;
+
+    if ($borrowernumber) {
+        my $q2 = qq|
+SELECT rating_value FROM ratings WHERE biblionumber = ? AND borrowernumber = ?|;
+        my $sth1 = C4::Context->dbh->prepare($q2);
+        $sth1->execute( $biblionumber, $borrowernumber );
+        my $res1 = $sth1->fetchrow_hashref();
+        $rating_hash{'rating_value'} = $res1->{"rating_value"};
+    }
+    else {
+        $rating_hash{rating_borrowernumber} = undef;
+        $rating_hash{rating_value}          = undef;
+    }
+
+#### %rating_hash
+    return \%rating_hash;
+}
+
+=head2 AddRating
+
+    my $rating_hashref = AddRating( $biblionumber, $borrowernumber, $rating_value );
+
+Add a rating for a bib
+
+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
+
+sub AddRating {
+    my ( $biblionumber, $borrowernumber, $rating_value ) = @_;
+    my $query =
+      qq| INSERT INTO ratings (borrowernumber,biblionumber,rating_value)
+        VALUES (?,?,?)|;
+    my $sth = C4::Context->dbh->prepare($query);
+    $sth->execute( $borrowernumber, $biblionumber, $rating_value );
+    my $rating = GetRating( $biblionumber, $borrowernumber );
+    return $rating;
+}
+
+=head2 ModRating
+
+    my $rating_hashref = ModRating( $biblionumber, $borrowernumber, $rating_value );
+
+Mod a rating for a bib
+
+=cut
+
+sub ModRating {
+    my ( $biblionumber, $borrowernumber, $rating_value ) = @_;
+    my $query =
+qq|UPDATE ratings SET rating_value = ? WHERE borrowernumber = ? AND biblionumber = ?|;
+    my $sth = C4::Context->dbh->prepare($query);
+    $sth->execute( $rating_value, $borrowernumber, $biblionumber );
+    my $rating = GetRating( $biblionumber, $borrowernumber );
+    return $rating;
+}
+
+=head2 DelRating
+
+    my $rating_hashref = DelRating( $biblionumber, $borrowernumber );
+
+Delete a rating for a bib
+
+=cut
+
+sub DelRating {
+    my ( $biblionumber, $borrowernumber ) = @_;
+    my $dbh = C4::Context->dbh;
+    my $query =
+      "delete from ratings where borrowernumber = ? and biblionumber = ?";
+    my $sth    = C4::Context->dbh->prepare($query);
+    my $rv     = $sth->execute( $borrowernumber, $biblionumber );
+    my $rating = GetRating( $biblionumber, undef );
+    return $rating;
+}
+
+1;
+__END__
index 21c657d..71eaeb0 100644 (file)
@@ -2813,6 +2813,21 @@ CREATE TABLE IF NOT EXISTS `social_data` (
   PRIMARY KEY  (`isbn`)
 ) 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
+    rating_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 */;
index 34ac684..b16b40f 100644 (file)
@@ -360,3 +360,4 @@ INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES
 INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('SocialNetworks','1','Enable/Disable social networks links in opac detail pages','','YesNo');
 INSERT INTO systempreferences (variable,value,options,explanation,type) VALUES('SubscriptionDuplicateDroppedInput','','','List of fields which must not be rewritten when a subscription is duplicated (Separated by pipe |)','Free');
 INSERT INTO systempreferences (variable,value,options,explanation,type) VALUES ('AutoResumeSuspendedHolds',  '1', NULL ,  'Allow suspended holds to be automatically resumed by a set date.',  'YesNo');
+INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES ('OpacStarRatings','all',NULL,'disable|all|details','Choice');
index 08f32b8..62c0e0b 100755 (executable)
@@ -4929,6 +4929,28 @@ if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) {
     INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES ('AllowPKIAuth','None','Use the field from a client-side SSL certificate to look a user in the Koha database','None|Common Name|emailAddress','Choice');
     });
     print "Upgrade to $DBversion done (Bug 6296 New System preference AllowPKIAuth)\n";
+}
+
+$DBversion = "3.07.00.XXX";
+if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) {
+    $dbh->do(
+        q | CREATE TABLE ratings (
+  borrowernumber int(11) NOT NULL,
+  biblionumber int(11) NOT NULL,
+  rating_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(
+q /INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES ('OpacStarRatings','disable',NULL,'disable|all|details','Choice') /
+    );
+
+    print
+"Upgrade to $DBversion done (Add 'ratings' table and 'OpacStarRatings' syspref)\n";
     SetVersion($DBversion);
 }
 
index d908f2c..6f48a53 100644 (file)
                   <a rel="license" href="http://creativecommons.org/licenses/by-sa/2.5/">Creative Commons Attribution-ShareAlike 2.5 License</a>
                   by the Bridge Consortium of Carleton College and St. Olaf College.</li>
               </ul>
+
+            <h2>jQuery Star Rating Plugin</h2>
+              <p>jQuery Star Rating Plugin v3.14 by <a href="http://www.fyneworks.com/">Fyneworks.com</a> is licensed under the <a target="_blank" href="http://en.wikipedia.org/wiki/MIT_License">MIT License</a> and the <a target="_blank" href="http://creativecommons.org/licenses/GPL/2.0/">GPL License</a>.</p>
+
+            <p>Copyright &copy; 2008 <a href="http://www.fyneworks.com/">Fyneworks.com</a></p>
+
         </div>
 
         <div id="translations">
index 54cc43a..da06bed 100644 (file)
@@ -6,6 +6,10 @@ OPAC:
               choices: opac-templates
             - theme on the OPAC.
         -
+
+
+
+
             - "The OPAC is located at http://"
             - pref: OPACBaseURL
               class: url
@@ -21,6 +25,14 @@ OPAC:
                   yes: Enable
                   no: Disable
             - "Koha OPAC as public. Private OPAC requires authentification before accessing the OPAC."
+        -
+            - "Show star-ratings on"
+            - pref: OpacStarRatings
+              choices:
+                  all: "results and details"
+                  disable: "no"
+                  details: "only details"
+            - "pages."
         -
             - pref: OpacMaintenance
               choices:
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 (file)
index 0000000..e89096b
--- /dev/null
@@ -0,0 +1,12 @@
+/* jQuery.Rating Plugin CSS - http://www.fyneworks.com/jquery/star-rating/ */
+div.rating-cancel,div.star-rating{float:left;width:15px;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 -32px!important}
+div.star-rating-hover a{background-position:0 -16px}
+/* 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.js b/koha-tmpl/opac-tmpl/prog/en/lib/jquery/plugins/jquery.rating.js
new file mode 100644 (file)
index 0000000..f4a22d5
--- /dev/null
@@ -0,0 +1,392 @@
+/*
+ ### 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
+ ###
+*/
+
+/*# AVOID COLLISIONS #*/
+;if(window.jQuery) (function($){
+/*# AVOID COLLISIONS #*/
+
+    // IE6 Background Image Fix
+    if ($.browser.msie) try { document.execCommand("BackgroundImageCache", false, true)} catch(e) { };
+    // Thanks to http://www.visualjquery.com/rating/rating_redux.html
+
+    // plugin initialization
+    $.fn.rating = function(options){
+        if(this.length==0) return this; // quick fail
+
+        // Handle API methods
+        if(typeof arguments[0]=='string'){
+            // Perform API methods on individual elements
+            if(this.length>1){
+                var args = arguments;
+                return this.each(function(){
+                    $.fn.rating.apply($(this), args);
+    });
+            };
+            // Invoke API method handler
+            $.fn.rating[arguments[0]].apply(this, $.makeArray(arguments).slice(1) || []);
+            // Quick exit...
+            return this;
+        };
+
+        // Initialize options for this call
+        var options = $.extend(
+            {}/* new object */,
+            $.fn.rating.options/* default options */,
+            options || {} /* just-in-time options */
+        );
+
+        // Allow multiple controls with the same name by making each call unique
+        $.fn.rating.calls++;
+
+        // loop through each matched element
+        this
+         .not('.star-rating-applied')
+            .addClass('star-rating-applied')
+        .each(function(){
+
+            // Load control parameters / find context / etc
+            var control, input = $(this);
+            var eid = (this.name || 'unnamed-rating').replace(/\[|\]/g, '_').replace(/^\_+|\_+$/g,'');
+            var context = $(this.form || document.body);
+
+            // FIX: http://code.google.com/p/jquery-star-rating-plugin/issues/detail?id=23
+            var raters = context.data('rating');
+            if(!raters || raters.call!=$.fn.rating.calls) raters = { count:0, call:$.fn.rating.calls };
+            var rater = raters[eid];
+
+            // if rater is available, verify that the control still exists
+            if(rater) control = rater.data('rating');
+
+            if(rater && control)//{// save a byte!
+                // add star to control if rater is available and the same control still exists
+                control.count++;
+
+            //}// save a byte!
+            else{
+                // create new control if first star or control element was removed/replaced
+
+                // Initialize options for this rater
+                control = $.extend(
+                    {}/* new object */,
+                    options || {} /* current call options */,
+                    ($.metadata? input.metadata(): ($.meta?input.data():null)) || {}, /* metadata options */
+                    { count:0, stars: [], inputs: [] }
+                );
+
+                // increment number of rating controls
+                control.serial = raters.count++;
+
+                // create rating element
+                rater = $('<span class="star-rating-control"/>');
+                input.before(rater);
+
+                // Mark element for initialization (once all stars are ready)
+                rater.addClass('rating-to-be-drawn');
+
+                // Accept readOnly setting from 'disabled' property
+                if(input.attr('disabled') || input.hasClass('disabled')) control.readOnly = true;
+
+                // Accept required setting from class property (class='required')
+                if(input.hasClass('required')) control.required = true;
+
+                // Create 'cancel' button
+                rater.append(
+                    control.cancel = $('<div class="rating-cancel"><a title="' + control.cancel + '">' + control.cancelValue + '</a></div>')
+                    .mouseover(function(){
+                        $(this).rating('drain');
+                        $(this).addClass('star-rating-hover');
+                        //$(this).rating('focus');
+                    })
+                    .mouseout(function(){
+                        $(this).rating('draw');
+                        $(this).removeClass('star-rating-hover');
+                        //$(this).rating('blur');
+                    })
+                    .click(function(){
+                     $(this).rating('select');
+                    })
+                    .data('rating', control)
+                );
+
+            }; // first element of group
+
+            // insert rating star
+            var star = $('<div class="star-rating rater-'+ control.serial +'"><a title="' + (this.title || this.value) + '">' + this.value + '</a></div>');
+            rater.append(star);
+
+            // inherit attributes from input element
+            if(this.id) star.attr('id', this.id);
+            if(this.className) star.addClass(this.className);
+
+            // Half-stars?
+            if(control.half) control.split = 2;
+
+            // Prepare division control
+            if(typeof control.split=='number' && control.split>0){
+                var stw = ($.fn.width ? star.width() : 0) || control.starWidth;
+                var spi = (control.count % control.split), spw = Math.floor(stw/control.split);
+                star
+                // restrict star's width and hide overflow (already in CSS)
+                .width(spw)
+                // move the star left by using a negative margin
+                // this is work-around to IE's stupid box model (position:relative doesn't work)
+                .find('a').css({ 'margin-left':'-'+ (spi*spw) +'px' })
+            };
+
+            // readOnly?
+            if(control.readOnly)//{ //save a byte!
+                // Mark star as readOnly so user can customize display
+                star.addClass('star-rating-readonly');
+            //}  //save a byte!
+            else//{ //save a byte!
+             // Enable hover css effects
+                star.addClass('star-rating-live')
+                 // Attach mouse events
+                    .mouseover(function(){
+                        $(this).rating('fill');
+                        $(this).rating('focus');
+                    })
+                    .mouseout(function(){
+                        $(this).rating('draw');
+                        $(this).rating('blur');
+                    })
+                    .click(function(){
+                        $(this).rating('select');
+                    })
+                ;
+            //}; //save a byte!
+
+            // set current selection
+            if(this.checked)   control.current = star;
+
+            // set current select for links
+            if(this.nodeName=="A"){
+    if($(this).hasClass('selected'))
+     control.current = star;
+   };
+
+            // hide input element
+            input.hide();
+
+            // backward compatibility, form element to plugin
+            input.change(function(){
+    $(this).rating('select');
+   });
+
+            // attach reference to star to input element and vice-versa
+            star.data('rating.input', input.data('rating.star', star));
+
+            // store control information in form (or body when form not available)
+            control.stars[control.stars.length] = star[0];
+            control.inputs[control.inputs.length] = input[0];
+            control.rater = raters[eid] = rater;
+            control.context = context;
+
+            input.data('rating', control);
+            rater.data('rating', control);
+            star.data('rating', control);
+            context.data('rating', raters);
+  }); // each element
+
+        // Initialize ratings (first draw)
+        $('.rating-to-be-drawn').rating('draw').removeClass('rating-to-be-drawn');
+
+        return this; // don't break the chain...
+    };
+
+    /*--------------------------------------------------------*/
+
+    /*
+        ### Core functionality and API ###
+    */
+    $.extend($.fn.rating, {
+        // Used to append a unique serial number to internal control ID
+        // each time the plugin is invoked so same name controls can co-exist
+        calls: 0,
+
+        focus: function(){
+            var control = this.data('rating'); if(!control) return this;
+            if(!control.focus) return this; // quick fail if not required
+            // find data for event
+            var input = $(this).data('rating.input') || $( this.tagName=='INPUT' ? this : null );
+   // focus handler, as requested by focusdigital.co.uk
+            if(control.focus) control.focus.apply(input[0], [input.val(), $('a', input.data('rating.star'))[0]]);
+        }, // $.fn.rating.focus
+
+        blur: function(){
+            var control = this.data('rating'); if(!control) return this;
+            if(!control.blur) return this; // quick fail if not required
+            // find data for event
+            var input = $(this).data('rating.input') || $( this.tagName=='INPUT' ? this : null );
+   // blur handler, as requested by focusdigital.co.uk
+            if(control.blur) control.blur.apply(input[0], [input.val(), $('a', input.data('rating.star'))[0]]);
+        }, // $.fn.rating.blur
+
+        fill: function(){ // fill to the current mouse position.
+            var control = this.data('rating'); if(!control) return this;
+            // do not execute when control is in read-only mode
+            if(control.readOnly) return;
+            // Reset all stars and highlight them up to this element
+            this.rating('drain');
+            this.prevAll().andSelf().filter('.rater-'+ control.serial).addClass('star-rating-hover');
+        },// $.fn.rating.fill
+
+        drain: function() { // drain all the stars.
+            var control = this.data('rating'); if(!control) return this;
+            // do not execute when control is in read-only mode
+            if(control.readOnly) return;
+            // Reset all stars
+            control.rater.children().filter('.rater-'+ control.serial).removeClass('star-rating-on').removeClass('star-rating-hover');
+        },// $.fn.rating.drain
+
+        draw: function(){ // set value and stars to reflect current selection
+            var control = this.data('rating'); if(!control) return this;
+            // Clear all stars
+            this.rating('drain');
+            // Set control value
+            if(control.current){
+                control.current.data('rating.input').attr('checked','checked');
+                control.current.prevAll().andSelf().filter('.rater-'+ control.serial).addClass('star-rating-on');
+            }
+            else
+             $(control.inputs).removeAttr('checked');
+            // Show/hide 'cancel' button
+            control.cancel[control.readOnly || control.required?'hide':'show']();
+            // Add/remove read-only classes to remove hand pointer
+            this.siblings()[control.readOnly?'addClass':'removeClass']('star-rating-readonly');
+        },// $.fn.rating.draw
+
+
+
+
+
+        select: function(value,wantCallBack){ // select a value
+
+                    // ***** MODIFICATION *****
+                    // Thanks to faivre.thomas - http://code.google.com/p/jquery-star-rating-plugin/issues/detail?id=27
+                    //
+                    // ***** LIST OF MODIFICATION *****
+                    // ***** added Parameter wantCallBack : false if you don't want a callback. true or undefined if you want postback to be performed at the end of this method'
+                    // ***** recursive calls to this method were like : ... .rating('select') it's now like .rating('select',undefined,wantCallBack); (parameters are set.)
+                    // ***** line which is calling callback
+                    // ***** /LIST OF MODIFICATION *****
+
+            var control = this.data('rating'); if(!control) return this;
+            // do not execute when control is in read-only mode
+            if(control.readOnly) return;
+            // clear selection
+            control.current = null;
+            // programmatically (based on user input)
+            if(typeof value!='undefined'){
+             // select by index (0 based)
+                if(typeof value=='number')
+                        return $(control.stars[value]).rating('select',undefined,wantCallBack);
+                // select by literal value (must be passed as a string
+                if(typeof value=='string')
+                    //return
+                    $.each(control.stars, function(){
+                        if($(this).data('rating.input').val()==value) $(this).rating('select',undefined,wantCallBack);
+                    });
+            }
+            else
+                control.current = this[0].tagName=='INPUT' ?
+                 this.data('rating.star') :
+                    (this.is('.rater-'+ control.serial) ? this : null);
+
+            // Update rating control state
+            this.data('rating', control);
+            // Update display
+            this.rating('draw');
+            // find data for event
+            var input = $( control.current ? control.current.data('rating.input') : null );
+            // click callback, as requested here: http://plugins.jquery.com/node/1655
+
+                    // **** MODIFICATION *****
+                    // Thanks to faivre.thomas - http://code.google.com/p/jquery-star-rating-plugin/issues/detail?id=27
+                    //
+                    //old line doing the callback :
+                    //if(control.callback) control.callback.apply(input[0], [input.val(), $('a', control.current)[0]]);// callback event
+                    //
+                    //new line doing the callback (if i want :)
+                    if((wantCallBack ||wantCallBack == undefined) && control.callback) control.callback.apply(input[0], [input.val(), $('a', control.current)[0]]);// callback event
+                    //to ensure retro-compatibility, wantCallBack must be considered as true by default
+                    // **** /MODIFICATION *****
+
+  },// $.fn.rating.select
+
+
+
+
+
+        readOnly: function(toggle, disable){ // make the control read-only (still submits value)
+            var control = this.data('rating'); if(!control) return this;
+            // setread-only status
+            control.readOnly = toggle || toggle==undefined ? true : false;
+            // enable/disable control value submission
+            if(disable) $(control.inputs).attr("disabled", "disabled");
+            else                       $(control.inputs).removeAttr("disabled");
+            // Update rating control state
+            this.data('rating', control);
+            // Update display
+            this.rating('draw');
+        },// $.fn.rating.readOnly
+
+        disable: function(){ // make read-only and never submit value
+            this.rating('readOnly', true, true);
+        },// $.fn.rating.disable
+
+        enable: function(){ // make read/write and submit value
+            this.rating('readOnly', false, false);
+        }// $.fn.rating.select
+
+ });
+
+    /*--------------------------------------------------------*/
+
+    /*
+        ### Default Settings ###
+        eg.: You can override default control like this:
+        $.fn.rating.options.cancel = 'Clear';
+    */
+    $.fn.rating.options = { //$.extend($.fn.rating, { options: {
+            cancel: 'Cancel Rating',   // advisory title for the 'cancel' link
+            cancelValue: '',           // value to submit when user click the 'cancel' link
+            split: 0,                  // split the star into how many parts?
+
+            // Width of star image in case the plugin can't work it out. This can happen if
+            // the jQuery.dimensions plugin is not available OR the image is hidden at installation
+            starWidth: 16//,
+
+            //NB.: These don't need to be pre-defined (can be undefined/null) so let's save some code!
+            //half:     false,         // just a shortcut to control.split = 2
+            //required: false,         // disables the 'cancel' button so user can only select one of the specified values
+            //readOnly: false,         // disable rating plugin interaction/ values cannot be changed
+            //focus:    function(){},  // executed when stars are focused
+            //blur:     function(){},  // executed when stars are focused
+            //callback: function(){},  // executed when a star is clicked
+ }; //} });
+
+    /*--------------------------------------------------------*/
+
+    /*
+        ### Default implementation ###
+        The plugin will attach itself to file inputs
+        with the class 'multi' when the page loads
+    */
+    $(function(){
+     $('input[type=radio].star').rating();
+    });
+
+
+
+/*# AVOID COLLISIONS #*/
+})(jQuery);
+/*# AVOID COLLISIONS #*/
index 36ef631..4520c90 100644 (file)
@@ -9,6 +9,9 @@
       {lang: '[% lang %]'}
     </script>
 [% END %]
+<script type="text/javascript" src="/opac-tmpl/prog/en/lib/jquery/plugins/jquery.rating.js"></script>
+<link rel="stylesheet" type="text/css" href="/opac-tmpl/prog/en/css/jquery.rating.css" />
+
 <script type="text/JavaScript" language="JavaScript">
 //<![CDATA[
     [% IF ( busc ) %]
         [% END %]
 
         $(".branch-info-tooltip-trigger").tooltip({delay: 100, position: "top right"});
+
+// -----------------------------------------------------
+// star-ratings code
+// -----------------------------------------------------
+// hide 'rate' button if javascript enabled
+
+$('input[name="rate_button"]').remove();
+
+$(function () {
+  $(".auto-submit-star").rating({
+    callback: function (value, link) {
+
+      // if the new value equals the old value, dont execute callback...
+      // just do nothing!
+      if ($("#rating_value").attr("value") != value) {
+
+        $(function () {
+
+          $.post("/cgi-bin/koha/opac-ratings-ajax.pl", {
+            rating_old_value: $("#rating_value").attr("value"),
+            borrowernumber: "[% borrowernumber %]",
+            biblionumber: "[% biblionumber %]",
+            rating_value: value,
+            auth_error: value,
+          }, function (data) {
+
+            if (data.auth_status != 'ok') {
+              window.alert('Your CGI session cookie is not current. Please refresh the page and try again.');
+            } else {
+              $("#rating_value").val(data.rating_value);
+
+              if (data.rating_value) {
+                $("#rating_value_text").text('your rating: ' + data.rating_value + ', ');
+              } else {
+                $("#rating_value_text").text('');
+              }
+
+              $("#rating_text").text('average rating: ' + data.rating_avg_int + ' (' + data.rating_total + ' votes)');
+
+            }
+          }, "json");
+        });
+      };
+    }
+  });
 });
+// -----------------------------------------------------
 
+});
 
 [% IF ( busc ) %]
 
@@ -215,7 +265,6 @@ YAHOO.util.Event.onContentReady("furtherm", function () {
                YAHOO.util.Event.addListener("furthersearches", "click", furthersearchesMenu.show, null, furthersearchesMenu);
                YAHOO.widget.Overlay.windowResizeEvent.subscribe(positionfurthersearchesMenu);
  });
-       
 //]]>
 </script>
 [% IF ( opacuserlogin ) %][% IF ( loggedinusername ) %][% IF ( TagsEnabled ) %]<style type="text/css">
@@ -488,6 +537,44 @@ YAHOO.util.Event.onContentReady("furtherm", function () {
         </span>
         [% END %][% END %][% END %]
 
+    [% IF ( OpacStarRatings != 'disable' ) %]
+        <form method="post" action="/cgi-bin/koha/opac-ratings.pl">
+        <div class="results_summary">
+
+      [% FOREACH i  IN [ 1 2 3 4 5  ] %]
+        [% IF rating_avg == i && borrowernumber %]
+            <input class="auto-submit-star" type="radio" name="rating"  value="[% i %]"  checked="checked" />
+        [% ELSIF rating_avg == i %]
+            <input class="auto-submit-star" type="radio" name="rating" value="[% i %]" checked="checked" disabled="disabled" />
+        [% ELSIF borrowernumber  %]
+            <input class="auto-submit-star" type="radio" name="rating" value="[% i %]" />
+        [% ELSE   %]
+            <input class="auto-submit-star" type="radio" name="rating" value="[% i %]" disabled="disabled" />
+        [% END %]
+      [% END %]
+
+<!-- define some hidden vars for ratings -->
+
+        <input  type="hidden" name='biblionumber'  value="[% biblionumber %]" />
+        <input  type="hidden" name='borrowernumber'  value="[% borrowernumber %]" />
+        <input  type="hidden" name='rating_value' id='rating_value' value="[% rating_value %]" />
+        <input  type="hidden" name='rating_total' id='rating_total' value="[% rating_total %]" />
+        <input  type="hidden" name='rating_avg_int' id='rating_avg_int' value="[% rating_avg_int %]" />
+
+        [% UNLESS ( rating_readonly ) %]&nbsp;  <INPUT name="rate_button" type="submit" value="Rate me">[% END %]&nbsp;
+
+        [% IF ( rating_value ) %]
+            <span id="rating_value_text">your rating: [% rating_value %], </span>
+        [% ELSE %]
+            <span id="rating_value_text"></span>
+        [% END %]
+
+            <span id="rating_text">average rating: [% rating_avg_int %] ([% rating_total %] votes)</span>
+
+        </div>
+        </FORM>
+    [% END %]
+
     [% IF ( BakerTaylorContentURL ) %]
         <span class="results_summary">
         <span class="label">Enhanced content: </span>
index d811d86..2c12d44 100644 (file)
@@ -6,8 +6,10 @@
     You did not specify any search criteria.
 [% END %]
 [% INCLUDE 'doc-head-close.inc' %]
-<link rel="alternate" type="application/rss+xml" title="[% LibraryName |html %] Search RSS Feed" href="[% OPACBaseURL %]/cgi-bin/koha/opac-search.pl?[% query_cgi |html %][% limit_cgi |html %]&amp;count=[% countrss |html %]&amp;sort_by=acqdate_dsc&amp;format=rss2" />
-
+<link rel="alternate" type="application/rss+xml" title="[% LibraryName |html %] Search RSS Feed" href="[% OPACBaseurl %]/cgi-bin/koha/opac-search.pl?[% query_cgi |html %][% limit_cgi |html %]&amp;count=[% countrss |html %]&amp;sort_by=acqdate_dsc&amp;format=rss2" />
+<script type="text/javascript" src="/opac-tmpl/prog/en/lib/jquery/jquery.js"></script>
+<script type="text/javascript" src="/opac-tmpl/prog/en/lib/jquery/plugins/jquery.rating.js"></script>
+<link rel="stylesheet" type="text/css" href="/opac-tmpl/prog/en/css/jquery.rating.css" />
 
 <script type="text/javascript" src="[% themelang %]/lib/jquery/plugins/jquery.checkboxes.min.js"></script>
 [% IF ( OpacHighlightedWords ) %]<script type="text/javascript" src="[% themelang %]/lib/jquery/plugins/jquery.highlight-3.js"></script>
@@ -234,6 +236,7 @@ $(document).ready(function(){
     [% IF OPACLocalCoverImages %]KOHA.LocalCover.GetCoverFromBibnumber(false);[% END %]
     [% IF ( GoogleJackets ) %]KOHA.Google.GetCoverFromIsbn();[% END %]
 });
+
 //]]>
 </script>
 </head>
@@ -481,8 +484,8 @@ $(document).ready(function(){
                     [% IF ( SEARCH_RESULT.intransitcount ) %] In transit ([% SEARCH_RESULT.intransitcount %]),[% END %]
                     </span>
                 </span>
-
                 [% END %]
+
                 [% IF ( SEARCH_RESULT.score_avg ) %]
                     <span class="result_summary">
                         <img src="[% themelang %]/../images/Star[% SEARCH_RESULT.score_int %].gif" title="" style="max-height: 15px;"/> <span style="font-size: 85%;">[% SEARCH_RESULT.score_avg %] / 5 (on [% SEARCH_RESULT.num_scores %] rates)</span>
@@ -500,6 +503,33 @@ $(document).ready(function(){
                         [% END %]
                     </span>
                 [% END %]
+
+
+                [% IF ( OpacStarRatings == 'all' ) %]
+                <div class="results_summary">
+
+[% FOREACH i  IN [ 1 2 3 4 5  ] %]
+
+[% IF ( SEARCH_RESULT.rating_avg == i ) %]
+    <input class="star" type="radio"  name="rating-[% SEARCH_RESULT.biblionumber %]" value="[% i %]" checked="checked" disabled="disabled"   />
+[% ELSE   %]
+    <input class="star" type="radio"  name="rating-[% SEARCH_RESULT.biblionumber %]" value="[% i %]" disabled="disabled"   />
+[% END %]
+
+[% END %]
+                <input type="hidden" name='biblionumber'  value="[% SEARCH_RESULT.biblionumber %]" />
+                <input type="hidden" name='loggedinuser'  value="[% loggedinuser %]" />
+
+                [% IF (  SEARCH_RESULT.rating_total ) > 0  %]
+                    <span id="rating_total_[% SEARCH_RESULT.biblionumber %]">&nbsp;&nbsp;([% SEARCH_RESULT.rating_total %] votes)</span>
+                [% ELSE %]
+                    </br>
+                [% END %]
+
+                </div>
+                [% END %]
+
+
                 [% IF ( LibraryThingForLibrariesID ) %]<div class="ltfl_reviews"></div>[% END %]
                 [% IF ( opacuserlogin ) %][% IF ( TagsEnabled ) %]
                                 [% IF ( TagsShowOnList ) %]
diff --git a/koha-tmpl/opac-tmpl/prog/images/delete.gif b/koha-tmpl/opac-tmpl/prog/images/delete.gif
new file mode 100755 (executable)
index 0000000..43c6ca8
Binary files /dev/null and b/koha-tmpl/opac-tmpl/prog/images/delete.gif differ
diff --git a/koha-tmpl/opac-tmpl/prog/images/star.gif b/koha-tmpl/opac-tmpl/prog/images/star.gif
new file mode 100644 (file)
index 0000000..d0948a7
Binary files /dev/null and b/koha-tmpl/opac-tmpl/prog/images/star.gif differ
index 796f3b0..11fae95 100755 (executable)
@@ -2,6 +2,7 @@
 
 # Copyright 2000-2002 Katipo Communications
 # Copyright 2010 BibLibre
+# Copyright 2011 KohaAloha, NZ
 #
 # This file is part of Koha.
 #
@@ -36,6 +37,7 @@ use C4::XISBN qw(get_xisbns get_biblionumber_from_isbn);
 use C4::External::Amazon;
 use C4::External::Syndetics qw(get_syndetics_index get_syndetics_summary get_syndetics_toc get_syndetics_excerpt get_syndetics_reviews get_syndetics_anotes );
 use C4::Review;
+use C4::Ratings;
 use C4::Members;
 use C4::VirtualShelves;
 use C4::XSLT;
@@ -549,7 +551,7 @@ my $subtitle         = GetRecordValue('subtitle', $record, GetFrameworkCode($bib
                      MARCAUTHORS             => $marcauthorsarray,
                      MARCSERIES              => $marcseriesarray,
                      MARCURLS                => $marcurlsarray,
-                    MARCHOSTS               => $marchostsarray,
+                     MARCHOSTS               => $marchostsarray,
                      norequests              => $norequests,
                      RequestOnOpac           => C4::Context->preference("RequestOnOpac"),
                      itemdata_ccode          => $itemfields{ccode},
@@ -559,6 +561,7 @@ my $subtitle         = GetRecordValue('subtitle', $record, GetFrameworkCode($bib
                      itemdata_itemnotes          => $itemfields{itemnotes},
                      authorised_value_images => $biblio_authorised_value_images,
                      subtitle                => $subtitle,
+                     OpacStarRatings         => C4::Context->preference("OpacStarRatings"),
     );
 
 if (C4::Context->preference("AlternateHoldingsField") && scalar @items == 0) {
@@ -629,6 +632,10 @@ if ( C4::Context->preference('ShowReviewer') and C4::Context->preference('ShowRe
 
 my $reviews = getreviews( $biblionumber, 1 );
 my $loggedincommenter;
+
+
+
+
 foreach ( @$reviews ) {
     my $borrowerData   = GetMember('borrowernumber' => $_->{borrowernumber});
     # setting some borrower info into this hash
@@ -640,6 +647,8 @@ foreach ( @$reviews ) {
     }
     $_->{userid}    = $borrowerData->{'userid'};
     $_->{cardnumber}    = $borrowerData->{'cardnumber'};
+    $_->{datereviewed} = format_date($_->{datereviewed});
+
     if ($borrowerData->{'borrowernumber'} eq $borrowernumber) {
                $_->{your_comment} = 1;
                $loggedincommenter = 1;
@@ -908,6 +917,17 @@ my $OpacExportOptions=C4::Context->preference("OpacExportOptions");
 my @export_options = split(/\|/,$OpacExportOptions);
 $template->{VARS}->{'export_options'} = \@export_options;
 
+if ( C4::Context->preference('OpacStarRatings') !~ /disable/ ) {
+    my $rating = GetRating( $biblionumber, $borrowernumber );
+    $template->param(
+        rating_value   => $rating->{'rating_value'},
+        rating_total   => $rating->{'rating_total'},
+        rating_avg     => $rating->{'rating_avg'},
+        rating_avg_int => $rating->{'rating_avg_int'},
+        borrowernumber => $borrowernumber
+    );
+}
+
 #Search for title in links
 my $marccontrolnumber   = GetMarcControlnumber ($record, $marcflavour);
 my $marcissns = GetMarcISSN ( $record, $marcflavour );
diff --git a/opac/opac-ratings-ajax.pl b/opac/opac-ratings-ajax.pl
new file mode 100755 (executable)
index 0000000..88f2634
--- /dev/null
@@ -0,0 +1,115 @@
+#!/usr/bin/perl
+
+# Copyright 2011 KohaAloha, 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 DESCRIPTION
+
+A script that takes an ajax json query, and then inserts or modifies a star-rating.
+
+=cut
+
+use strict;
+use warnings;
+
+use CGI;
+use CGI::Cookie;  # need to check cookies before having CGI parse the POST request
+
+use C4::Auth qw(:DEFAULT check_cookie_auth);
+use C4::Context;
+use C4::Debug;
+use C4::Output 3.02 qw(:html :ajax pagination_bar);
+use C4::Ratings;
+use JSON;
+
+my $is_ajax = is_ajax();
+
+my ( $query, $auth_status );
+if ($is_ajax) {
+    ( $query, $auth_status ) = &ajax_auth_cgi( {} );
+}
+else {
+    $query = CGI->new();
+}
+
+my $biblionumber     = $query->param('biblionumber');
+my $rating_value     = $query->param('rating_value');
+my $rating_old_value = $query->param('rating_old_value');
+
+my ( $template, $loggedinuser, $cookie );
+if ($is_ajax) {
+    $loggedinuser = C4::Context->userenv->{'number'};
+}
+else {
+    ( $template, $loggedinuser, $cookie ) = get_template_and_user(
+        {
+            template_name   => "opac-detail.tt",
+            query           => $query,
+            type            => "opac",
+            authnotrequired => 0,                    # auth required to add tags
+            debug           => 1,
+        }
+    );
+}
+
+my $rating;
+
+undef $rating_value if $rating_value eq '';
+
+if ( !$rating_value ) {
+#### delete
+    $rating = DelRating( $biblionumber, $loggedinuser );
+}
+
+elsif ( $rating_value and !$rating_old_value ) {
+#### insert
+    $rating = AddRating( $biblionumber, $loggedinuser, $rating_value );
+}
+
+elsif ( $rating_value ne $rating_old_value ) {
+#### mod
+    $rating = ModRating( $biblionumber, $loggedinuser, $rating_value );
+}
+
+my %js_reply = (
+    rating_total   => $rating->{'rating_total'},
+    rating_avg     => $rating->{'rating_avg'},
+    rating_avg_int => $rating->{'rating_avg_int'},
+    rating_value   => $rating->{'rating_value'},
+    auth_status    => $auth_status,
+
+);
+
+my $json_reply = JSON->new->encode( \%js_reply );
+
+#### $rating
+#### %js_reply
+#### $json_reply
+
+output_ajax_with_http_headers( $query, $json_reply );
+exit;
+
+# a ratings specific ajax return sub, returns CGI object, and an 'auth_success' value
+sub ajax_auth_cgi {
+    my $needed_flags = shift;
+    my %cookies      = fetch CGI::Cookie;
+    my $input        = CGI->new;
+    my $sessid = $cookies{'CGISESSID'}->value || $input->param('CGISESSID');
+    my ( $auth_status, $auth_sessid ) =
+      check_cookie_auth( $sessid, $needed_flags );
+    return $input, $auth_status;
+}
diff --git a/opac/opac-ratings.pl b/opac/opac-ratings.pl
new file mode 100755 (executable)
index 0000000..b2bcf6f
--- /dev/null
@@ -0,0 +1,65 @@
+#!/usr/bin/perl
+
+# Copyright 2011 KohaAloha, 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
+
+A non-javascript method to add/modify a biblio's rating, called from opac-detail.pl
+
+note: there is currently no 'delete rating' functionality in this script
+
+=cut
+
+use strict;
+use warnings;
+use CGI;
+use CGI::Cookie;
+use C4::Auth qw(:DEFAULT check_cookie_auth);
+use C4::Context;
+use C4::Output;
+use C4::Dates qw(format_date);
+use C4::Biblio;
+use C4::Ratings;
+use C4::Debug;
+
+my $query = CGI->new();
+my $a     = $query->Vars;
+####  $a
+my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
+    {
+        template_name   => "",
+        query           => $query,
+        type            => "opac",
+        authnotrequired => 0,        # auth required to add tags
+        debug           => 0,
+    }
+);
+
+my $biblionumber     = $query->param('biblionumber');
+my $rating_old_value = $query->param('rating_value');
+my $rating_value     = $query->param('rating');
+my $rating;
+
+if ( !$rating_old_value ) {
+    $rating = AddRating( $biblionumber, $loggedinuser, $rating_value );
+}
+else {
+    $rating = ModRating( $biblionumber, $loggedinuser, $rating_value );
+}
+print $query->redirect(
+    "/cgi-bin/koha/opac-detail.pl?biblionumber=$biblionumber");
index 85ca721..176bc12 100755 (executable)
@@ -1,7 +1,8 @@
 #!/usr/bin/perl
 
-# Copyright 2008 Garry Collum and the Koha Koha Development team
+# Copyright 2008 Garry Collum and the Koha Development team
 # Copyright 2010 BibLibre
+# Copyright 2011 KohaAloha, NZ
 #
 # This file is part of Koha.
 #
@@ -37,12 +38,13 @@ use C4::Koha;
 use C4::Tags qw(get_tags);
 use C4::Branch; # GetBranches
 use C4::SocialData;
+use C4::Ratings;
+
 use POSIX qw(ceil floor strftime);
 use URI::Escape;
 use Storable qw(thaw freeze);
 use Business::ISBN;
 
-
 my $DisplayMultiPlaceHold = C4::Context->preference("DisplayMultiPlaceHold");
 # create a new CGI object
 # FIXME: no_undef_params needs to be tested
@@ -113,6 +115,9 @@ elsif (C4::Context->preference("marcflavour") eq "MARC21" ) {
 $template->param( 'AllowOnShelfHolds' => C4::Context->preference('AllowOnShelfHolds') );
 $template->param( 'OPACNoResultsFound' => C4::Context->preference('OPACNoResultsFound') );
 
+$template->param(
+    OpacStarRatings => C4::Context->preference("OpacStarRatings") );
+
 if (C4::Context->preference('BakerTaylorEnabled')) {
     $template->param(
         BakerTaylorEnabled  => 1,
@@ -121,6 +126,7 @@ if (C4::Context->preference('BakerTaylorEnabled')) {
         BakerTaylorBookstoreURL => C4::Context->preference('BakerTaylorBookstoreURL'),
     );
 }
+
 if (C4::Context->preference('TagsEnabled')) {
     $template->param(TagsEnabled => 1);
     foreach (qw(TagsShowOnList TagsInputOnList)) {
@@ -520,6 +526,7 @@ for (my $i=0;$i<@servers;$i++) {
             }
         }
 
+
         my $tag_quantity;
         if (C4::Context->preference('TagsEnabled') and
             $tag_quantity = C4::Context->preference('TagsShowOnList')) {
@@ -530,6 +537,7 @@ for (my $i=0;$i<@servers;$i++) {
                                         limit=>$tag_quantity });
             }
         }
+
         if (C4::Context->preference('COinSinOPACResults')) {
             foreach (@newresults) {
                 my $record = GetMarcBiblio($_->{'biblionumber'});
@@ -551,6 +559,17 @@ for (my $i=0;$i<@servers;$i++) {
             }
         }
 
+
+        if ( C4::Context->preference('OpacStarRatings') eq 'all' ) {
+            foreach my $res (@newresults) {
+                my $rating = GetRating( $res->{'biblionumber'}, $borrowernumber );
+                $res->{'rating_value'}  = $rating->{'rating_value'};
+                $res->{'rating_total'}  = $rating->{'rating_total'};
+                $res->{'rating_avg'}    = $rating->{'rating_avg'};
+                $res->{'rating_avg_int'} = $rating->{'rating_avg_int'};
+            }
+        }
+
         if ($results_hashref->{$server}->{"hits"}){
             $total = $total + $results_hashref->{$server}->{"hits"};
         }
@@ -773,4 +792,5 @@ if (C4::Context->preference('GoogleIndicTransliteration')) {
         $template->param('GoogleIndicTransliteration' => 1);
 }
 
+    $template->param( borrowernumber    => $borrowernumber);
 output_with_http_headers $cgi, $cookie, $template->output, $content_type;
diff --git a/t/db_dependent/Ratings.t b/t/db_dependent/Ratings.t
new file mode 100755 (executable)
index 0000000..23bde86
--- /dev/null
@@ -0,0 +1,63 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Test::More tests => 12;
+use C4::Members;
+
+BEGIN {
+
+    use FindBin;
+    use C4::Ratings;
+    use_ok('C4::Ratings');
+
+    DelRating( 1, 901 );
+    DelRating( 1, 902 );
+
+    my $rating5 = GetRating( 1, undef );
+    ok( defined $rating5, 'get a rating, without borrowernumber' );
+
+    my $borrower102 = GetMember( borrowernumber => 102);
+    my $borrower103 = GetMember( borrowernumber => 103);
+    SKIP: {
+        skip 'Missing test borrowers, skipping specific tests', 10 unless ( defined $borrower102 && defined $borrower103 );
+        my $rating1 = AddRating( 1, 102, 3 );
+        my $rating2 = AddRating( 1, 103, 4 );
+        my $rating3 = ModRating( 1, 102, 5 );
+        my $rating4 = GetRating( 1, 103 );
+        my $rating6 = DelRating( 1, 102 );
+        my $rating7 = DelRating( 1, 103 );
+
+        ok( defined $rating1, 'add a rating' );
+        ok( defined $rating2, 'add another rating' );
+        ok( defined $rating3, 'update a rating' );
+        ok( defined $rating4, 'get a rating, with borrowernumber' );
+        ok( defined $rating6,                'delete a rating' );
+        ok( defined $rating7,                'delete another rating' );
+
+        ok( $rating3->{'rating_avg'} == '4', "get a bib's average(float) rating" );
+        ok( $rating3->{'rating_avg_int'} == 4.5, "get a bib's average(int) rating" );
+        ok( $rating3->{'rating_total'} == 2, "get a bib's total number of ratings" );
+        ok( $rating3->{'rating_value'} == 5, "verify user's bib rating" );
+    }
+
+}
+
+=c
+
+mason@xen1:~/g/head$ perl t/db_dependent/Ratings.t
+1..12
+ok 1 - use C4::Ratings;
+ok 2 - add a rating
+ok 3 - add another rating
+ok 4 - update a rating
+ok 5 - get a rating, with borrowernumber
+ok 6 - get a rating, without borrowernumber
+ok 7 - get a bib's average(float) rating
+ok 8 - get a bib's average(int) rating
+ok 9 - get a bib's total number of ratings
+ok 10 - verify user's bib rating
+ok 11 - delete a rating
+ok 12 - delete another rating
+
+=cut