--- /dev/null
+package C4::External::OverDrive;
+
+# Copyright (c) 2013 ByWater
+#
+# 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 JSON;
+use Koha::Cache;
+use HTTP::Request;
+use HTTP::Request::Common;
+use LWP::Authen::Basic;
+use LWP::UserAgent;
+
+BEGIN {
+ require Exporter;
+ our $VERSION = 3.07.00.049;
+ our @ISA = qw( Exporter ) ;
+ our @EXPORT = qw(
+ IsOverDriveEnabled
+ GetOverDriveToken
+ );
+}
+
+sub _request {
+ my ( $request ) = @_;
+ my $ua = LWP::UserAgent->new( "Koha " . C4::Context->KOHAVERSION );
+
+ my $response;
+ eval {
+ $response = $ua->request( $request ) ;
+ };
+ if ( $@ ) {
+ warn "OverDrive request failed: $@";
+ return;
+ }
+
+ return $response;
+}
+
+=head1 NAME
+
+C4::External::OverDrive - Retrieve OverDrive content availability information
+
+=head2 FUNCTIONS
+
+This module provides content search for OverDrive,
+
+=over
+
+=item IsOverDriveEnabled
+
+Returns 1 if all of the necessary system preferences for OverDrive are set.
+
+=back
+
+=cut
+
+sub IsOverDriveEnabled {
+ return (
+ C4::Context->preference( 'OverDriveClientKey' ) &&
+ C4::Context->preference( 'OverDriveClientSecret' )
+ );
+}
+
+=item GetOverDriveToken
+
+Fetches an OAuth2 auth token for the OverDrive API, reusing an existing token in
+Memcache if possible.
+
+Returns the token ( as "bearer ..." ) or undef on failure.
+
+=back
+
+=cut
+
+sub GetOverDriveToken {
+ my $key = C4::Context->preference( 'OverDriveClientKey' );
+ my $secret = C4::Context->preference( 'OverDriveClientSecret' );
+
+ return unless ( $key && $secret ) ;
+
+ my $cache;
+
+ eval { $cache = Koha::Cache->new() };
+
+ my $token;
+ $cache and $token = $cache->get_from_cache( "overdrive_token" ) and return $token;
+
+ my $request = HTTP::Request::Common::POST( 'https://oauth.overdrive.com/token', [
+ grant_type => 'client_credentials'
+ ] ) ;
+ $request->header( Authorization => LWP::Authen::Basic->auth_header( $key, $secret ) );
+
+ my $response = _request( $request ) or return;
+ if ( $response->header('Content-Type') !~ m!application/json! ) {
+ warn "Could not connect to OverDrive: " . $response->message;
+ return;
+ }
+ my $contents = from_json( $response->decoded_content );
+
+ if ( !$response->is_success ) {
+ warn "Could not log into OverDrive: " . ( $contents ? $contents->{'error_description'} : $response->decoded_content );
+ return;
+ }
+
+ $token = $contents->{'token_type'} . ' ' . $contents->{'access_token'};
+
+ # Fudge factor to prevent spurious failures
+ $cache->set_in_cache( 'overdrive_token', $token, $contents->{'expires_in'} - 5 );
+
+ return $token;
+}
+
+1;
+__END__
+
+=head1 NOTES
+
+=cut
+
+=head1 AUTHOR
+
+Jesse Weaver <pianohacker@gmail.com>
+
+=cut
SetVersion($DBversion);
}
+$DBversion = "3.13.00.XXX";
+if(CheckVersion($DBversion)) {
+ $dbh->do(
+"INSERT IGNORE INTO systempreferences (variable,value,explanation,options,type) VALUES('OverDriveClientKey','','Client key for OverDrive integration','30','Free')"
+ );
+ $dbh->do(
+"INSERT IGNORE INTO systempreferences (variable,value,explanation,options,type) VALUES('OverDriveClientSecret','','Client key for OverDrive integration','30','YesNo')"
+ );
+ $dbh->do(
+"INSERT IGNORE INTO systempreferences (variable,value,explanation,options,type) VALUES('OpacDriveLibraryID','','Library ID for OverDrive integration','','Integer')"
+ );
+ print "Upgrade to $DBversion done (Bug 10320 - Show results from library's OverDrive collection in OPAC search)\n";
+ SetVersion($DBversion);
+}
+
=head1 FUNCTIONS
=head2 TableExists($table)
yes: Enable
no: "Don't enable"
- the ability to use Koha Plugins. Note, the plugin system must also be enabled in the Koha configuration file to be fully enabled.
+ OverDrive:
+ -
+ - Include OverDrive availability information with the client key
+ - pref: OverDriveClientKey
+ - and client secret
+ - pref: OverDriveClientSecret
+ - .
+ -
+ - "Show items from the OverDrive catalog of library #"
+ - pref: OverDriveLibraryID
+ - .
--- /dev/null
+if ( typeof KOHA == "undefined" || !KOHA ) {
+ var KOHA = {};
+}
+
+KOHA.OverDrive = ( function() {
+ var proxy_base_url = '/cgi-bin/koha/svc/overdrive_proxy';
+ var library_base_url = 'http://api.overdrive.com/v1/libraries/';
+ return {
+ Get: function( url, params, callback ) {
+ $.ajax( {
+ type: 'GET',
+ url: url.replace( /https?:\/\/api.overdrive.com\/v1/, proxy_base_url ),
+ dataType: 'json',
+ data: params,
+ error: function( xhr, error ) {
+ try {
+ callback( JSON.parse( xhr.responseText ));
+ } catch ( e ) {
+ callback( {error: xhr.responseText || true} );
+ }
+ },
+ success: callback
+ } );
+ },
+ GetCollectionURL: function( library_id, callback ) {
+ if ( KOHA.OverDrive.collection_url ) {
+ callback( KOHA.OverDrive.collection_url );
+ return;
+ }
+
+ KOHA.OverDrive.Get(
+ library_base_url + library_id,
+ {},
+ function ( data ) {
+ if ( data.error ) {
+ callback( data );
+ return;
+ }
+
+ KOHA.OverDrive.collection_url = data.links.products.href;
+
+ callback( data.links.products.href );
+ }
+ );
+ },
+ Search: function( library_id, q, limit, offset, callback ) {
+ KOHA.OverDrive.GetCollectionURL( library_id, function( data ) {
+ if ( data.error ) {
+ callback( data );
+ return;
+ }
+
+ KOHA.OverDrive.Get(
+ data,
+ {q: q, limit: limit, offset: offset},
+ callback
+ );
+ } );
+ }
+ };
+} )();
.thumbnail-shelfbrowser span {
margin: 0px auto;
}
+<<<<<<< HEAD
.sorting_asc {
padding-right: 19px;
background: url("../../images/asc.gif") no-repeat scroll right center #EEEEEE;
padding-right: 19px;
background: #EEEEEE none;
}
+
+#overdrive-results {
+ font-weight: bold;
+ padding-left: 1em;
+}
+
+.throbber {
+ vertical-align: middle;
+}
--- /dev/null
+if ( typeof KOHA == "undefined" || !KOHA ) {
+ var KOHA = {};
+}
+
+KOHA.OverDrive = ( function() {
+ var proxy_base_url = '/cgi-bin/koha/svc/overdrive_proxy';
+ var library_base_url = 'http://api.overdrive.com/v1/libraries/';
+ return {
+ Get: function( url, params, callback ) {
+ $.ajax( {
+ type: 'GET',
+ url: url.replace( /https?:\/\/api.overdrive.com\/v1/, proxy_base_url ),
+ dataType: 'json',
+ data: params,
+ error: function( xhr, error ) {
+ try {
+ callback( JSON.parse( xhr.responseText ));
+ } catch ( e ) {
+ callback( {error: xhr.responseText || true} );
+ }
+ },
+ success: callback
+ } );
+ },
+ GetCollectionURL: function( library_id, callback ) {
+ if ( KOHA.OverDrive.collection_url ) {
+ callback( KOHA.OverDrive.collection_url );
+ return;
+ }
+
+ KOHA.OverDrive.Get(
+ library_base_url + library_id,
+ {},
+ function ( data ) {
+ if ( data.error ) {
+ callback( data );
+ return;
+ }
+
+ KOHA.OverDrive.collection_url = data.links.products.href;
+
+ callback( data.links.products.href );
+ }
+ );
+ },
+ Search: function( library_id, q, limit, offset, callback ) {
+ KOHA.OverDrive.GetCollectionURL( library_id, function( data ) {
+ if ( data.error ) {
+ callback( data );
+ return;
+ }
+
+ KOHA.OverDrive.Get(
+ data,
+ {q: q, limit: limit, offset: offset},
+ callback
+ );
+ } );
+ }
+ };
+} )();
--- /dev/null
+[% INCLUDE 'doc-head-open.inc' %]
+[% IF ( LibraryNameTitle ) %][% LibraryNameTitle %][% ELSE %]Koha online[% END %] catalog › OverDrive search for '[% q | html %]'
+[% INCLUDE 'doc-head-close.inc' %]
+<script type="text/javascript" src="[% themelang %]/js/overdrive.js"></script>
+<script type="text/javascript">
+var querystring = "[% q |replace( "'", "\'" ) |replace( '\n', '\\n' ) |replace( '\r', '\\r' ) |html %]";
+var results_per_page = [% OPACnumSearchResults %];
+
+function fetch_availability( prod, $tr ) {
+ var $availability_summary = $( '<span class="results_summary"></span>' );
+ $tr.find( '.info' ).append( $availability_summary );
+ $availability_summary.html( '<span class="label">Availability: </span> Loading...' );
+
+ KOHA.OverDrive.Get(
+ prod.links.availability.href,
+ {},
+ function ( data ) {
+ if ( data.error ) return;
+
+ $availability_summary.html( '<span class="label">Copies available: </span><span class="available"><strong>' + data.copiesAvailable + '</strong> out of ' + data.copiesOwned + '</span>' );
+
+ if ( data.numberOfHolds ) {
+ $availability_summary.find( '.available' ).append( ', waiting holds: <strong>' + data.numberOfHolds + '</strong>' );
+ }
+
+ $tr.find( '.info' ).append( '<span class="results_summary actions"><span class="label">Actions: </span><a href="http://' + prod.contentDetails[0].href + '" ' + ( data.copiesAvailable ? ' class="addtocart">Check out' : ' class="hold">Place hold' ) + '</a></span>' );
+ }
+ );
+}
+
+function search( offset ) {
+ $( '#overdrive-status' ).html( 'Searching OverDrive... <img class="throbber" src="/opac-tmpl/lib/jquery/plugins/themes/classic/throbber.gif" /></span>' );
+
+ KOHA.OverDrive.Search( "[% OverDriveLibraryID %]", querystring, results_per_page, offset, function( data ) {
+ if ( data.error ) {
+ $( '#overdrive-status' ).html( '<strong class="unavailable">Error searching OverDrive collection.</strong>' );
+ return;
+ }
+
+ if ( !data.totalItems ) {
+ $( '#overdrive-status' ).html( '<strong>No results found in the library\'s OverDrive collection.</strong>' );
+ return;
+ }
+
+ $( '#results tbody' ).empty();
+
+ $( '#overdrive-status' ).html( '<strong>Found ' + data.totalItems + ' results in the library\'s OverDrive collection.</strong>' );
+
+ for ( var i = 0; data.products[i]; i++ ) {
+ var prod = data.products[i];
+ var results = [];
+
+ results.push( '<tr>' );
+
+ results.push( '<td class="info"><a class="title" href="http://', prod.contentDetails[0].href, '">' );
+ results.push( prod.title );
+ if ( prod.subtitle ) results.push( ', ', prod.subtitle );
+ results.push( '</a>' );
+ results.push( '<p>by ', prod.primaryCreator.name, '</p>' );
+ results.push( '<span class="results_summary"><span class="label">Type: </span>', prod.mediaType, '</span>' );
+ if ( prod.starRating ) results.push( '<span class="results_summary"><span class="label">Average rating: <img src="[% themelang %]/../images/Star', Math.round( parseInt( prod.starRating )), '.gif" title="" style="max-height: 15px; vertical-align: bottom"/></span>' );
+ results.push( '</td>' );
+
+ results.push( '<td>' );
+ if ( prod.images.thumbnail ) {
+ results.push( '<a href="http://', prod.contentDetails[0].href, '">' );
+ results.push( '<img class="thumbnail" src="', prod.images.thumbnail.href, '" />' );
+ results.push( '</a>' );
+ }
+ results.push( '</td>' );
+
+ results.push( '</tr>' );
+ var $tr = $( results.join( '' ));
+ $( '#results tbody' ).append( $tr );
+
+ fetch_availability( prod, $tr );
+ }
+
+ $( '#results tr:odd' ).addClass( 'highlight' );
+
+ var pages = [];
+ var cur_page = offset / results_per_page;
+ var max_page = Math.floor( data.totalItems / results_per_page );
+
+ if ( cur_page != 0 ) {
+ pages.push( '<a class="nav" href="#" data-offset="' + (offset - results_per_page) + '"><< Previous</a>' );
+ }
+
+ for ( var page = Math.max( 0, cur_page - 9 ); page <= Math.min( max_page, cur_page + 9 ); page++ ) {
+ if ( page == cur_page ) {
+ pages.push( ' <span class="current">' + ( page + 1 ) + '</span>' );
+ } else {
+ pages.push( ' <a class="nav" href="#" data-offset="' + ( page * results_per_page ) + '">' + ( page + 1 ) + '</a>' );
+ }
+ }
+
+ if ( cur_page < max_page ) {
+ pages.push( ' <a class="nav" href="#" data-offset="' + (offset + results_per_page) + '">Next >></a>' );
+ }
+
+ if ( pages.length > 1 ) $( '#top-pages, #bottom-pages' ).find( '.pages' ).html( pages.join( '' ) );
+ } );
+}
+
+$( document ).ready( function() {
+ $( '#breadcrumbs p' )
+ .append( ' ' )
+ .append( '<span id="overdrive-status"></span>' );
+
+ $( document ).on( 'click', 'a.nav', function() {
+ search( $( this ).data( 'offset' ) );
+ return false;
+ });
+
+ search( 0 );
+} );
+</script>
+<style>
+.actions a.addtocart {
+ display: inline;
+}
+</style>
+</head>
+<body>
+[% IF ( OpacNav ) %]
+<div id="doc3" class="yui-t1">
+[% ELSE %]
+<div id="doc3" class="yui-t7">
+[% END %]
+ <div id="bd">
+[% INCLUDE 'masthead.inc' %]
+
+ <h1>OverDrive search for '[% q | html %]'</h1>
+ <div id="breadcrumbs">
+ <p></p>
+ </div>
+
+ <div id="yui-main"><div class="yui-b searchresults">
+ <div id="top-pages">
+ <div class="pages">
+ </div>
+ </div>
+ <table id="results">
+ <tbody>
+ </tbody>
+ </table>
+ <div id="bottom-pages">
+ <div class="pages">
+ </div>
+ </div>
+ </div></div>
+
+[% IF ( OpacNav ) %]
+<div class="yui-b"><div id="opacnav" class="container">
+[% INCLUDE 'navigation.inc' %]
+</div></div>
+[% END %]
+
+
+</div>
+[% INCLUDE 'opac-bottom.inc' %]
<link rel="stylesheet" type="text/css" href="[% themelang %]/css/jquery.rating.css" />
[% END %]
+<script type="text/javascript" src="[% themelang %]/js/overdrive.js"></script>
<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>
[% END %]<script type="text/javascript">
[% END %]
$("#holdDetails").hide();
-[% IF ( query_desc ) %][% IF ( OpacHighlightedWords ) %]var query_desc = "[% query_desc |replace("'", "\'") |replace('\n', '\\n') |replace('\r', '\\r') |html %]";
+[% IF ( query_desc ) %]
+ var query_desc = "[% query_desc |replace("'", "\'") |replace('\n', '\\n') |replace('\r', '\\r') |html %]";
+ var querystring = "[% querystring |replace("'", "\'") |replace('\n', '\\n') |replace('\r', '\\r') |html %]";
+ [% IF ( OpacHighlightedWords ) %]
q_array = query_desc.split(" ");
// ensure that we don't have "" at the end of the array, which can
// break the highlighter
}
highlightOn();
$("#highlight_toggle_on" ).hide().click(function() {highlightOn() ;});
- $("#highlight_toggle_off").show().click(function() {highlightOff();});[% END %][% END %]
+ $("#highlight_toggle_off").show().click(function() {highlightOff();});
+ [% END %]
+ [% IF ( OverDriveEnabled ) %]
+ var $overdrive_results = $( '<span id="overdrive-results">Searching OverDrive... <img class="throbber" src="/opac-tmpl/lib/jquery/plugins/themes/classic/throbber.gif" /></span>' );
+ $( '#breadcrumbs p' ).eq(0)
+ .append( ' ' )
+ .append( $overdrive_results );
+ KOHA.OverDrive.Search( "[% OverDriveLibraryID %]", querystring, 1, 0, function( data ) {
+ if ( data.error ) {
+ $overdrive_results.html( 'Error searching OverDrive collection' );
+ return;
+ }
+
+ if ( data.totalItems ) {
+ $overdrive_results.html( 'Found <a href="/cgi-bin/koha/opac-overdrive-search.pl?q=' + escape( querystring ) + '">' + data.totalItems + ' results</a> in OverDrive collection' );
+ } else {
+ $overdrive_results.remove();
+ }
+ } );
+ [% END %]
+[% END %]
[% IF ( TagsInputEnabled && loggedinusername ) %]
$("#tagsel_tag").click(function(){
--- /dev/null
+#!/usr/bin/perl
+
+# Copyright 2009 BibLibre SARL
+#
+# 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 CGI;
+
+use C4::Auth qw(:DEFAULT get_session);
+use C4::Output;
+
+my $cgi = new CGI;
+
+# Getting the template and auth
+my ($template, $loggedinuser, $cookie)
+= get_template_and_user({template_name => "opac-overdrive-search.tmpl",
+ query => $cgi,
+ type => "opac",
+ authnotrequired => 1,
+ flagsrequired => {borrowers => 1},
+ debug => 1,
+ });
+
+$template->{'VARS'}->{'q'} = $cgi->param('q');
+$template->{'VARS'}->{'limit'} = C4::Context->preference('OPACnumSearchResults') || 20;
+$template->{'VARS'}->{'OPACnumSearchResults'} = C4::Context->preference('OPACnumSearchResults') || 20;
+$template->{'VARS'}->{'OverDriveLibraryID'} = C4::Context->preference('OverDriveLibraryID');
+
+output_html_with_http_headers $cgi, $cookie, $template->output;
use C4::Branch; # GetBranches
use C4::SocialData;
use C4::Ratings;
+use C4::External::OverDrive;
use POSIX qw(ceil floor strftime);
use URI::Escape;
$template->{VARS}->{IDreamBooksReadometer} = C4::Context->preference('IDreamBooksReadometer');
$template->{VARS}->{IDreamBooksResults} = C4::Context->preference('IDreamBooksResults');
+if ($offset == 0 && IsOverDriveEnabled()) {
+ $template->param(OverDriveEnabled => 1);
+ $template->param(OverDriveLibraryID => C4::Context->preference('OverDriveLibraryID'));
+}
+
$template->param( borrowernumber => $borrowernumber);
output_with_http_headers $cgi, $cookie, $template->output, $content_type;
--- /dev/null
+#!/usr/bin/perl
+
+# Copyright 2013 ByWater
+#
+# 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 NAME
+
+svc/overdrive_proxy: Proxy OAuth'd requests to OverDrive
+
+=head1 SYNOPSIS
+
+svc/overdrive_proxy/libraries/9001 -> https://api.overdrive.com/v1/libraries/9001
+
+=head1 DESCRIPTION
+
+This service proxies incoming requests to the OverDrive OAuth API, to keep the
+JS side from having to deal with cross-origin/authentication issues.
+
+=cut
+
+use strict;
+use warnings;
+
+use CGI qw(-oldstyle_urls);
+use JSON;
+
+use C4::Context;
+use C4::External::OverDrive;
+use C4::Output;
+
+my $query = new CGI;
+
+my $token;
+
+if ( !IsOverDriveEnabled() || !( $token = GetOverDriveToken() ) ) {
+ print $query->header(
+ -status => '400 Bad Request',
+ -content
+ );
+
+ print to_json({
+ error => 'invalid_client',
+ error_description => 'OverDrive login failed'
+ });
+
+ exit;
+}
+
+my $request = HTTP::Request::Common::GET( "https://api.overdrive.com/v1" . $query->path_info . '?' . $query->query_string );
+$request->header( Authorization => $token );
+
+my $ua = LWP::UserAgent->new( "Koha " . C4::Context->KOHAVERSION );
+
+my $response = $ua->request( $request ) ;
+if ( $response->code eq '500' ) {
+ print $query->header(
+ -status => '500 Internal Server Error'
+ );
+
+ warn "OverDrive request failed: " . $response->message;
+ print to_json({
+ error => 'invalid_client',
+ error_description => 'OverDrive request failed'
+ });
+
+ exit;
+}
+
+output_with_http_headers $query, undef, $response->content, 'json', $response->status_line;