View | Details | Raw Unified | Return to bug 33697
Collapse All | Expand All

(-)a/Koha/ExternalContent/RecordedBooks.pm (-119 lines)
Lines 1-119 Link Here
1
# Copyright 2016 Catalyst
2
#
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
package Koha::ExternalContent::RecordedBooks;
19
20
use Modern::Perl;
21
use Carp qw( croak );
22
23
use base qw(Koha::ExternalContent);
24
use WebService::ILS::RecordedBooks::PartnerPatron;
25
use WebService::ILS::RecordedBooks::Partner;
26
use C4::Context;
27
28
__PACKAGE__->mk_accessors(qw(domain is_identified));
29
30
=head1 NAME
31
32
Koha::ExternalContent::RecordedBooks
33
34
=head1 SYNOPSIS
35
36
    use Koha::ExternalContent::RecordedBooks;
37
    my $rb_client = Koha::ExternalContent::RecordedBooks->new();
38
    my $rb_auth_url = $od_client->auth_url();
39
40
=head1 DESCRIPTION
41
42
A (very) thin wrapper around C<WebService::ILS::RecordedBooks::Patron>
43
44
Takes "RecordedBooks*" Koha preferences
45
46
=cut
47
48
=head2 Class Methods
49
50
=cut
51
52
=head3 new
53
54
my $rb_client = Koha::ExternalContent::RecordedBooks->new();
55
56
Create the object for interacting with RecordedBooks
57
58
=cut
59
60
sub new {
61
    my $class  = shift;
62
    my $params = shift || {};
63
64
    my $self = $class->SUPER::new($params);
65
    unless ($params->{client}) {
66
        my $client_secret  = C4::Context->preference('RecordedBooksClientSecret')
67
          or croak("RecordedBooksClientSecret pref not set");
68
        my $library_id     = C4::Context->preference('RecordedBooksLibraryID')
69
          or croak("RecordedBooksLibraryID pref not set");
70
        my $domain         = C4::Context->preference('RecordedBooksDomain');
71
        my $patron = $params->{koha_session_id} ? $self->koha_patron : undef;
72
        my $email;
73
        if ($patron) {
74
            $email = $patron->email
75
              or $self->logger->warn("User with no email, cannot identify with RecordedBooks");
76
        }
77
        my $client;
78
        if ($email) {
79
            local $@;
80
            $client = eval { WebService::ILS::RecordedBooks::PartnerPatron->new(
81
                client_secret     => $client_secret,
82
                library_id        => $library_id,
83
                domain            => $domain,
84
                user_id           => $email,
85
            ) };
86
            $self->logger->warn("Invalid RecordedBooks user $email ($@)") if $@;
87
            $self->is_identified($client);
88
        }
89
        $client ||= WebService::ILS::RecordedBooks::Partner->new(
90
                client_secret     => $client_secret,
91
                library_id        => $library_id,
92
                domain            => $domain,
93
        );
94
        $self->client( $client );
95
    }
96
    return $self;
97
}
98
99
=head1 METHODS
100
101
L<WebService::ILS::RecordedBooks::PartnerPatron> methods used without mods:
102
103
=over 4
104
105
=item C<error_message()>
106
107
=back
108
109
=cut
110
111
use vars qw{$AUTOLOAD};
112
sub AUTOLOAD {
113
    my $self = shift;
114
    (my $method = $AUTOLOAD) =~ s/.*:://;
115
    return $self->client->$method(@_);
116
}
117
sub DESTROY { }
118
119
1;
(-)a/installer/data/mysql/atomicupdate/bug_33697.pl (+16 lines)
Line 0 Link Here
1
use Modern::Perl;
2
3
return {
4
    bug_number => "33697",
5
    description => "Remove RecordedBooks (rbdigital) integration",
6
    up => sub {
7
        my ($args) = @_;
8
        my ($dbh, $out) = @$args{qw(dbh out)};
9
        for my $pref_name ( qw( RecordedBooksClientSecret RecordedBooksDomain RecordedBooksLibraryID ) ) {
10
            $dbh->do(q{
11
                DELETE FROM systempreferences
12
                WHERE variable=?
13
            }, undef, $pref_name) == 1 && say $out "Removed system preference '$pref_name'";
14
        }
15
    },
16
};
(-)a/installer/data/mysql/mandatory/sysprefs.sql (-3 lines)
Lines 302-310 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
302
('IndependentBranchesTransfers','0', NULL, 'Allow non-superlibrarians to transfer items between libraries','YesNo'),
302
('IndependentBranchesTransfers','0', NULL, 'Allow non-superlibrarians to transfer items between libraries','YesNo'),
303
('IntranetAddMastheadLibraryPulldown','0', NULL, 'Add a library select pulldown menu on the staff header search','YesNo'),
303
('IntranetAddMastheadLibraryPulldown','0', NULL, 'Add a library select pulldown menu on the staff header search','YesNo'),
304
('IntranetCatalogSearchPulldown','0', NULL, 'Show a search field pulldown for \"Search the catalog\" boxes','YesNo'),
304
('IntranetCatalogSearchPulldown','0', NULL, 'Show a search field pulldown for \"Search the catalog\" boxes','YesNo'),
305
('RecordedBooksClientSecret','','30','Client key for RecordedBooks integration','Free'),
306
('RecordedBooksDomain','','','RecordedBooks domain','Free'),
307
('RecordedBooksLibraryID','','','Library ID for RecordedBooks integration','Integer'),
308
('OnSiteCheckouts','0','','Enable/Disable the on-site checkouts feature','YesNo'),
305
('OnSiteCheckouts','0','','Enable/Disable the on-site checkouts feature','YesNo'),
309
('OnSiteCheckoutsForce','0','','Enable/Disable the on-site for all cases (Even if a user is debarred, etc.)','YesNo'),
306
('OnSiteCheckoutsForce','0','','Enable/Disable the on-site for all cases (Even if a user is debarred, etc.)','YesNo'),
310
('OnSiteCheckoutAutoCheck','0','','Enable/Do not enable onsite checkout by default if last checkout was an onsite checkout','YesNo'),
307
('OnSiteCheckoutAutoCheck','0','','Enable/Do not enable onsite checkout by default if last checkout was an onsite checkout','YesNo'),
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/enhanced_content.pref (-12 lines)
Lines 377-394 Enhanced content: Link Here
377
            - for user access to OverDrive. <br />
377
            - for user access to OverDrive. <br />
378
            - If you enable access you must have a SIP connection registered with
378
            - If you enable access you must have a SIP connection registered with
379
            - OverDrive for patron authentication against Koha
379
            - OverDrive for patron authentication against Koha
380
    RecordedBooks:
381
        -
382
            - Include RecordedBooks availability information with the client secret
383
            - pref: RecordedBooksClientSecret
384
            - .
385
        -
386
            - Show items from the RecordedBooks catalog of library ID
387
            - pref: RecordedBooksLibraryID
388
            - .
389
        -
390
            - RecordedBooks domain
391
            - pref: RecordedBooksDomain
392
    Coce cover images cache:
380
    Coce cover images cache:
393
        -
381
        -
394
            - pref: OpacCoce
382
            - pref: OpacCoce
(-)a/koha-tmpl/opac-tmpl/bootstrap/css/src/opac.scss (-1 lines)
Lines 2276-2282 nav { Link Here
2276
}
2276
}
2277
2277
2278
#overdrive-results,
2278
#overdrive-results,
2279
#recordedbooks-results,
2280
#openlibrary-results {
2279
#openlibrary-results {
2281
    font-weight: bold;
2280
    font-weight: bold;
2282
    padding-left: 1em;
2281
    padding-left: 1em;
(-)a/koha-tmpl/opac-tmpl/bootstrap/en/includes/opac-bottom.inc (-6 lines)
Lines 272-283 Link Here
272
</script>
272
</script>
273
[% END %]
273
[% END %]
274
274
275
[% IF Koha.Preference('RecordedBooksClientSecret') && Koha.Preference('RecordedBooksLibraryID') %]
276
<script>
277
  var SPINNER_THROBBER = "[% interface | html %]/[% theme | html %]/images/spinner-small.gif";
278
</script>
279
[% END %]
280
281
[% Asset.js("lib/js-cookie/js.cookie-3.0.1.min.js") | $raw %]
275
[% Asset.js("lib/js-cookie/js.cookie-3.0.1.min.js") | $raw %]
282
<script>
276
<script>
283
$(document).ready(function() {
277
$(document).ready(function() {
(-)a/koha-tmpl/opac-tmpl/bootstrap/en/includes/recordedbooks-checkout.inc (-15 lines)
Lines 1-15 Link Here
1
<div id="recordedbooks-checkout" class="modal" tabindex="-1" role="dialog" aria-labelledby="recordedbooks-checkout-label" aria-hidden="true">
2
    <div class="modal-header">
3
        <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
4
        <h3 id="recordedbooks-checkout-label">Checkout</h3>
5
    </div>
6
    <form action="#" method="post" id="recordedbooks-checkout-form">
7
        <div class="modal-body">
8
                <input type="hidden" name="id" value="" />
9
        </div>
10
        <div class="modal-footer">
11
            <input type="submit" class="btn btn-default recordedbooks-checkout-submit" value="Checkout" />
12
            <a href="#" data-dismiss="modal" aria-hidden="true" class="cancel">Cancel</a>
13
        </div>
14
    </form> <!-- /#recordedbooks-checkout-form -->
15
</div>  <!-- /#recordedbooks-checkout  -->
(-)a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-recordedbooks-search.tt (-178 lines)
Lines 1-178 Link Here
1
[% USE raw %]
2
[% USE Asset %]
3
[% USE Koha %]
4
[% USE AdditionalContents %]
5
[% SET OpacNav = AdditionalContents.get( location => "OpacNav", lang => lang, library => logged_in_user.branchcode || default_branch, blocktitle => 0 ) %]
6
[% SET OpacNavBottom = AdditionalContents.get( location => "OpacNavBottom", lang => lang, library => logged_in_user.branchcode || default_branch, blocktitle => 0 ) %]
7
[% INCLUDE 'doc-head-open.inc' %]
8
<title>RecordedBooks search for '[% q | html %]' &rsaquo; [% IF ( LibraryNameTitle ) %][% LibraryNameTitle | html %][% ELSE %]Koha online[% END %] catalog</title>
9
[% INCLUDE 'doc-head-close.inc' %]
10
[% BLOCK cssinclude %]
11
[% END %]
12
</head>
13
[% INCLUDE 'bodytag.inc' bodyid='recordedbooks-results-page' bodyclass='scrollto' %]
14
[% INCLUDE 'masthead.inc' %]
15
16
    <div class="main">
17
        <nav id="breadcrumbs" aria-label="Breadcrumb" class="breadcrumbs">
18
            <ol class="breadcrumb">
19
                <li class="breadcrumb-item">
20
                    <a href="/cgi-bin/koha/opac-main.pl">Home</a>
21
                </li>
22
                <li class="breadcrumb-item active">
23
                    <a href="#" aria-current="page">RecordedBooks search for '[% q | html %]'</a>
24
                </li>
25
            </ol>
26
        </nav> <!-- /#breadcrumbs -->
27
28
        <div class="container-fluid">
29
            <div class="row">
30
                <div class="col-lg-2">
31
                    [% IF ( OpacNav || OpacNavBottom ) %]
32
                        [% INCLUDE 'navigation.inc' %]
33
                    [% END %]
34
                </div>
35
                <div class="col-lg-10 order-first order-md-first order-lg-2">
36
                    <div id="recordedbooks-results-content" class="maincontent searchresults">
37
                        <h1>RecordedBooks search for '[% q | html %]'</h1>
38
                            [% UNLESS ( logged_in_user ) %]
39
                                 <h2 class="rb_login">Sign in to view availability and checkout items or place holds</h2>
40
                            [% END %]
41
42
                            <div id="breadcrumbs">
43
                                <p></p>
44
                            </div>
45
46
                            <div id="top-pages">
47
                                <nav class="pagination pagination-sm noprint" aria-label="Search results pagination">
48
                                </nav>
49
                            </div>
50
51
                            <table id="recordedbooks-results-list" class="table table-striped">
52
                                <tbody>
53
                                </tbody>
54
                            </table>
55
56
                            <div id="bottom-pages">
57
                                <nav class="pagination pagination-sm noprint" aria-label="Search results pagination">
58
                                </nav>
59
                            </div>
60
61
                    </div> <!-- / #recordedbooks-results-content -->
62
                </div> <!-- / .col-lg-10 -->
63
            </div> <!-- / .row -->
64
        </div> <!-- / .container-fluid -->
65
    </div> <!-- / .main -->
66
67
[% INCLUDE 'recordedbooks-checkout.inc' %]
68
69
[% INCLUDE 'opac-bottom.inc' %]
70
[% BLOCK jsinclude %]
71
    [% Asset.js("js/recordedbooks.js") | $raw %]
72
    <script>
73
        var querystring = "[% q |replace( "'", "\'" ) |replace( '\n', '\\n' ) |replace( '\r', '\\r' ) |html %]";
74
        var results_per_page = [% OPACnumSearchResults || 20 | html %];
75
76
        function search( page ) {
77
            $( '#recordedbooks-status' ).html( MSG_SEARCHING.format("RecordedBooks") + ' <img class="throbber" src="[% interface | html %]/[% theme | html %]/images/spinner-small.gif" /></span>' );
78
79
            KOHA.RecordedBooks.search( querystring, results_per_page, page, function( data ) {
80
                if ( data.error ) {
81
                    $( '#recordedbooks-status' ).html( '<strong class="unavailable">' + MSG_ERROR_SEARCHING_COLLECTION.format("RecordedBooks") + ': ' + data.error + '</strong>' );
82
                    return;
83
                }
84
85
                if ( !data.total ) {
86
                    $( '#recordedbooks-status' ).html( '<strong>' + MSG_NO_RESULTS_FOUND_IN_COLLECTION.format("RecordedBooks") + '</strong>' );
87
                    return;
88
                }
89
90
                $( '#recordedbooks-results-list tbody' ).empty();
91
92
                $( '#recordedbooks-status' ).html( '<strong>' + MSG_RESULTS_FOUND_IN_COLLECTION.format(data.total, "RecordedBooks") + '</strong>' );
93
94
                for ( var i = 0; data.items[i]; i++ ) {
95
                    var prod = data.items[i];
96
                    var results = [];
97
                    results.push( '<tr>' );
98
99
                    results.push( '<td class="info"><span class="title">' );
100
                    if (prod.url) results.push( '<a href="', prod.url, '" target="recordedbooks">' );
101
                    results.push( prod.title );
102
                    if (prod.url) results.push( '</a>' );
103
                    results.push( '</span>' );
104
                    results.push( '<p>' + MSG_BY + ' ', prod.author, '</p>' );
105
                    if (prod.description) results.push( '<p>' + prod.description, '</p>' );
106
                    results.push( '<span class="results_summary mediatype"><span class="label">' + MSG_TYPE + ': </span>', prod.media, '</span>' );
107
108
                    results.push( '</td>' );
109
110
                    results.push( '<td>' );
111
                    if ( prod.images && prod.images.medium ) {
112
                        if (prod.url) results.push( '<a href="', prod.url, '" target="recordedbooks">' );
113
                        results.push( '<img class="thumbnail" src="', prod.images.medium, '" />' );
114
                        if (prod.url) results.push( '</a>' );
115
                    }
116
                    results.push( '</td>' );
117
118
                    results.push( '</tr>' );
119
                    var $tr = $( results.join( '' ));
120
                    $( '#recordedbooks-results-list tbody' ).append( $tr );
121
122
                    $tr.find( '.info' ).each(function() {
123
                        KOHA.RecordedBooks.add_actions(this, prod.isbn);
124
                    });
125
                }
126
127
                $( '#recordedbooks-results-list tr:odd' ).addClass( 'highlight' );
128
129
                var pages = [];
130
131
                var max_page = Math.floor( data.total / results_per_page );
132
                if (data.total == page*results_per_page) max_page++;
133
134
                if ( page != 1 ) {
135
                    pages.push( '<li class="page-item"><a class="page-link od-nav" href="#" data-page="' + (page - 1) + '">&laquo; ' + MSG_PREVIOUS + '</a></li>' );
136
                }
137
138
                for ( var p = Math.max( 0, page - 9 ); p <= Math.min( max_page, p + 9 ); p++ ) {
139
                    if ( p == page ) {
140
                        pages.push( ' <li class="page-item disabled"><a class="page-link" href="#">' + ( p + 1 ) + '</a></li>' );
141
                    } else {
142
                        pages.push( ' <li class="page-item"><a class="page-link od-nav" href="#" data-page="' +  p + '">' + p + '</a></li>' );
143
                    }
144
                }
145
146
                if ( page < max_page ) {
147
                    pages.push( ' <li class="page-item"><a class="page-link od-nav" href="#" data-page="' + (page + 1) + '">' + MSG_NEXT + ' &raquo;</a></li>' );
148
                }
149
150
                if ( pages.length > 1 ) $( '#top-pages, #bottom-pages' ).find( '.pagination' ).html( '<ul class="pagination">' + pages.join( '' ) + '</ul>');
151
152
                if( KOHA.RecordedBooks.is_identified() == false ){
153
                    $("#breadcrumbs").before('<h3 class="rb_register"><a href="https://[% Koha.Preference('RecordedBooksDomain') | uri %]">To see availability you must register for RecordedBooks using your cardnumber and the email associated with your Koha account</a></h3>');
154
                }
155
156
            } );
157
        }
158
159
        $( document ).ready( function() {
160
            $( '#breadcrumbs p' )
161
                .append( ' ' )
162
                .append( '<span id="recordedbooks-status"></span>' );
163
164
            $( document ).on( 'click', 'a.od-nav', function() {
165
                search( $( this ).data( 'page' ) );
166
                return false;
167
            });
168
169
            [% IF ( logged_in_user ) %]
170
            KOHA.RecordedBooks.with_account_details("#breadcrumbs", function() {
171
                search( 1 );
172
            });
173
            [% ELSE %]
174
                search( 1 );
175
            [% END %]
176
        } );
177
    </script>
178
[% END %]
(-)a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-results.tt (-19 lines)
Lines 9-15 Link Here
9
9
10
[% IF firstPage %]
10
[% IF firstPage %]
11
     [% SET OverDriveEnabled = Koha.Preference('OverDriveLibraryID') && Koha.Preference('OverDriveClientKey') && Koha.Preference('OverDriveClientSecret') %]
11
     [% SET OverDriveEnabled = Koha.Preference('OverDriveLibraryID') && Koha.Preference('OverDriveClientKey') && Koha.Preference('OverDriveClientSecret') %]
12
     [% SET RecordedBooksEnabled = Koha.Preference('RecordedBooksLibraryID') && Koha.Preference('RecordedBooksClientSecret') %]
13
[% END %]
12
[% END %]
14
13
15
[% INCLUDE 'doc-head-open.inc' %]
14
[% INCLUDE 'doc-head-open.inc' %]
Lines 582-588 Link Here
582
        <script src="https://ltfl.librarything.com/forlibraries/widget.js?id=[% LibraryThingForLibrariesID | html %]&amp;systype=koha"></script>
581
        <script src="https://ltfl.librarything.com/forlibraries/widget.js?id=[% LibraryThingForLibrariesID | html %]&amp;systype=koha"></script>
583
    [% END %]
582
    [% END %]
584
    [% IF ( OverDriveEnabled ) %][% Asset.js("js/overdrive.js") | $raw %][% END %]
583
    [% IF ( OverDriveEnabled ) %][% Asset.js("js/overdrive.js") | $raw %][% END %]
585
    [% IF ( RecordedBooksEnabled ) %][% Asset.js("js/recordedbooks.js") | $raw %][% END %]
586
    [% Asset.js("js/authtoresults.js") | $raw %]
584
    [% Asset.js("js/authtoresults.js") | $raw %]
587
    [% Asset.js("lib/hc-sticky.js") | $raw %]
585
    [% Asset.js("lib/hc-sticky.js") | $raw %]
588
    [% IF ( OpacHighlightedWords ) %]
586
    [% IF ( OpacHighlightedWords ) %]
Lines 836-858 Link Here
836
                        }
834
                        }
837
                    } );
835
                    } );
838
                [% END # /IF OverDriveEnabled %]
836
                [% END # /IF OverDriveEnabled %]
839
                [% IF ( RecordedBooksEnabled ) %]
840
                    var $recordedbooks_results = $( '<div id="recordedbooks-results">' + MSG_SEARCHING.format('RecordedBooks') + ' <img class="throbber" src="[% interface | html %]/[% theme | html %]/images/spinner-small.gif" /></div>' );
841
                    $( '#numresults' ) .after( $recordedbooks_results );
842
                    KOHA.RecordedBooks.search( querystring, [% OPACnumSearchResults || "null" | html %], null, function( data ) {
843
                        if ( data.error ) {
844
                            $recordedbooks_results.html( MSG_ERROR_SEARCHING_COLLECTION.format('RecordedBooks')  + ': ' + data.error);
845
                            return;
846
                        }
847
848
                        // data.total can be either 42 or "60+"
849
                        if ( typeof(data.total) === 'string' && data.total.charAt(0) > 0 || typeof(data.total) === 'number' && data.total > 0 ) {
850
                            $recordedbooks_results.html( '<a href="/cgi-bin/koha/opac-recordedbooks-search.pl?q=' + encodeURIComponent( querystring ) + '">' + MSG_RESULTS_FOUND_IN_COLLECTION.format(data.total, 'RecordedBooks') + '</a>' );
851
                        } else {
852
                            $recordedbooks_results.remove();
853
                        }
854
                    } );
855
                [% END # /IF RecordedBooksEnabled %]
856
                [% IF ( OpenLibrarySearch ) %]
837
                [% IF ( OpenLibrarySearch ) %]
857
                    var $openlibrary_results = $( '<div id="openlibrary-results">' + MSG_SEARCHING.format('OpenLibrary' ) + ' <img class="throbber" src="[% interface | html %]/[% theme | html %]/images/spinner-small.gif" /></div>' );
838
                    var $openlibrary_results = $( '<div id="openlibrary-results">' + MSG_SEARCHING.format('OpenLibrary' ) + ' <img class="throbber" src="[% interface | html %]/[% theme | html %]/images/spinner-small.gif" /></div>' );
858
                    $( '#numresults' ) .after( $openlibrary_results );
839
                    $( '#numresults' ) .after( $openlibrary_results );
(-)a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt (-26 lines)
Lines 312-329 Link Here
312
                                    <a class="nav-link" id="opac-user-overdrive-tab" data-toggle="tab" role="tab" aria-controls="opac-user-overdrive" aria-selected="false" href="#opac-user-overdrive">OverDrive account</a>
312
                                    <a class="nav-link" id="opac-user-overdrive-tab" data-toggle="tab" role="tab" aria-controls="opac-user-overdrive" aria-selected="false" href="#opac-user-overdrive">OverDrive account</a>
313
                                </li>
313
                                </li>
314
                            [% END %]
314
                            [% END %]
315
                            [% IF ( RecordedBooksCirculation ) %]
316
                                <li class="nav-item" role="presentation">
317
                                    <a class="nav-link" id="opac-user-recordedbooks-tab" data-toggle="tab" role="tab" aria-controls="opac-user-recordedbooks" aria-selected="false" href="#opac-user-recordedbooks">RecordedBooks account</a>
318
                                </li>
319
                            [% END %]
320
                        </ul>
315
                        </ul>
321
                        <div class="tab-content">
316
                        <div class="tab-content">
322
317
323
                            <div id="opac-user-overdrive" class="tab-pane" role="tabpanel" aria-labelledby="opac-user-overdrive-tab">
318
                            <div id="opac-user-overdrive" class="tab-pane" role="tabpanel" aria-labelledby="opac-user-overdrive-tab">
324
                            </div>
319
                            </div>
325
                            <div id="opac-user-recordedbooks" class="tab-pane" role="tabpanel" aria-labelledby="opac-user-recordedbooks-tab">
326
                            </div>
327
                            <div id="opac-user-checkouts" class="tab-pane active" role="tabpanel" aria-labelledby="opac-user-checkouts-tab">
320
                            <div id="opac-user-checkouts" class="tab-pane active" role="tabpanel" aria-labelledby="opac-user-checkouts-tab">
328
                                [% IF ( issues_count ) %]
321
                                [% IF ( issues_count ) %]
329
                                    <form id="renewselected" action="/cgi-bin/koha/opac-renew.pl" method="post">
322
                                    <form id="renewselected" action="/cgi-bin/koha/opac-renew.pl" method="post">
Lines 1070-1078 Link Here
1070
        [% INCLUDE 'overdrive-login.inc' %]
1063
        [% INCLUDE 'overdrive-login.inc' %]
1071
    [% END %]
1064
    [% END %]
1072
[% END %]
1065
[% END %]
1073
[% IF ( RecordedBooksCirculation ) %]
1074
[% INCLUDE 'recordedbooks-checkout.inc' %]
1075
[% END %]
1076
1066
1077
[% INCLUDE 'opac-bottom.inc' %]
1067
[% INCLUDE 'opac-bottom.inc' %]
1078
[% BLOCK jsinclude %]
1068
[% BLOCK jsinclude %]
Lines 1437-1456 Link Here
1437
    });
1427
    });
1438
    </script>
1428
    </script>
1439
    [% END %]
1429
    [% END %]
1440
    [% IF RecordedBooksCirculation %]
1441
        [% Asset.js("js/recordedbooks.js") | $raw %]
1442
        <script>
1443
            $(document).ready(function() {
1444
                [% IF ( recordedbooks_error ) %]
1445
                    KOHA.RecordedBooks.display_error("#opac-user-recordedbooks", "[% recordedbooks_error.dquote | html %]");
1446
                [% END %]
1447
                [% IF ( recordedbooks_tab ) %]
1448
                    $("#opac-user-views a[href='#opac-user-recordedbooks']").tab("show");
1449
                [% END %]
1450
                $("#opac-user-recordedbooks").each( function() {
1451
                    KOHA.RecordedBooks.display_account_details(this);
1452
                } );
1453
            });
1454
        </script>
1455
    [% END %]
1456
[% END %]
1430
[% END %]
(-)a/koha-tmpl/opac-tmpl/bootstrap/js/recordedbooks.js (-334 lines)
Lines 1-334 Link Here
1
if ( typeof KOHA == "undefined" || !KOHA ) {
2
    var KOHA = {};
3
}
4
5
KOHA.RecordedBooks = new function() {
6
    var svc_url = '/cgi-bin/koha/svc/recordedbooks';
7
8
    var error_div = $('<div class="recordedbooks-error">');
9
    function display_error ( error ) {
10
        error_div.text(error);
11
    }
12
13
    var details = null;
14
15
    function is_identified() {
16
        return details ? details.is_identified : false;
17
    }
18
19
    var checkout_popup = null;
20
    $( document ).ready(function() {
21
        checkout_popup = $("#recordedbooks-checkout");
22
    });
23
24
    function display_account (container, data) {
25
        if (!data.is_identified) {
26
            return;
27
        }
28
29
        if (data.checkouts) {
30
            var checkouts_div = $('<div class="recordedbooks-div">').html('<h3>' + MSG_CHECKOUTS + '</h3>');
31
            var items = data.checkouts.items;
32
            var checkouts_list;
33
            if (items.length == 0) {
34
                checkouts_list = MSG_NO_CHECKOUTS;
35
            } else {
36
                checkouts_list = $('<ul class="recordedbooks-list">');
37
                data.checkouts.items.forEach(function(item) {
38
                    item_line(checkouts_list, item);
39
                });
40
            }
41
            checkouts_div.append(checkouts_list);
42
            $(container).append(checkouts_div);
43
        }
44
45
        if (data.holds) {
46
            var holds_div = $('<div class="recordedbooks-div">').html('<h3>' + MSG_HOLDS + '</h3>');
47
            var items = data.holds.items;
48
            var holds_list;
49
            if (items.length == 0) {
50
                holds_list = MSG_NO_HOLDS;
51
            } else {
52
                holds_list = $('<ul class="recordedbooks-list">');
53
                data.holds.items.forEach(function(item) {
54
                    item_line(holds_list, item);
55
                });
56
            }
57
            holds_div.append(holds_list);
58
            $(container).append(holds_div);
59
        }
60
    }
61
62
    function item_line(ul_el, item) {
63
        var line = $('<li class="recordedbooks-item">');
64
        if (item.images) {
65
            var thumb_url = item.images.small;
66
            if (thumb_url) {
67
                $('<img class="recordedbooks-item-thumbnail">')
68
                    .attr("src", thumb_url)
69
                    .appendTo(line);
70
            }
71
        }
72
        $('<div class="recordedbooks-item-title">')
73
            .text(item.title)
74
            .appendTo(line);
75
        $('<div class="recordedbooks-item-subtitle">')
76
            .text(item.subtitle)
77
            .appendTo(line);
78
        $('<div class="recordedbooks-item-author">')
79
            .text(item.author)
80
            .appendTo(line);
81
        if (item.files && item.files.length > 0) {
82
            downloads = $('<div class="recordedbooks-item-author">')
83
                .text("Downloads")
84
                .appendTo(line);
85
            render_downloads(downloads, item.files);
86
        }
87
        var actions = $('<span class="actions">');
88
        display_actions(actions, item.isbn);
89
        $('<div id="action_'+item.isbn+'" class="actions-menu">')
90
            .append(actions)
91
            .appendTo(line);
92
        $('<span id="waiting_'+item.isbn+'" style="display:none;"><img class="throbber" src="' + SPINNER_THROBBER + '" /></span>').appendTo(line);
93
        $(ul_el).append(line);
94
    }
95
96
    function render_downloads(el, files) {
97
        if (files.length == 0) return;
98
        var file_spec = files.shift();
99
        if (/^https?:\/\/api\./.test(file_spec.url)) {
100
            $.ajax({
101
                dataType: "json",
102
                url: file_spec.url,
103
                success: function (data) {
104
                    append_download_link(el, data.url, data.id);
105
                    render_downloads(el, files);
106
                },
107
                error: function(jqXHR, textStatus, errorThrown) {
108
                    display_error(errorThrown);
109
                }
110
            });
111
        } else {
112
            append_download_link(el, file_spec.url, file_spec.filename);
113
            render_downloads(el, files);
114
        }
115
    }
116
    function append_download_link(el, url, text) {
117
        var p = $("<p>");
118
        $( '<a href="' + url + '" target="recordedbooks">' )
119
            .text(text)
120
            .appendTo(p);
121
        el.append(p);
122
    }
123
124
    function svc_ajax ( method, params, success_callback, callback_for_error_too ) {
125
        // remove when jquery is upgraded
126
        for (var key in params) {
127
            if (params[key] === null) delete params[key];
128
        }
129
        return $.ajax({
130
            method: method,
131
            dataType: "json",
132
            url: svc_url,
133
            data: params,
134
            success: function (data) {
135
                if (data.error && !callback_for_error_too) {
136
                    display_error(data.error);
137
                }
138
                success_callback(data);
139
            },
140
            error: function(jqXHR, textStatus, errorThrown) {
141
                if (callback_for_error_too) {
142
                    success_callback({error: errorThrown});
143
                    return;
144
                }
145
                display_error(errorThrown);
146
            }
147
        });
148
    }
149
150
    function load_account_details ( callback ) {
151
        svc_ajax('get', { action: "account" }, function(data) {
152
            details = data;
153
            callback(data);
154
        });
155
    }
156
157
    function item_action (params, el) {
158
        var isbn = params.isbn;
159
        $("#action_"+isbn).hide();
160
        $("#waiting_"+isbn).show();
161
        svc_ajax('post', params, function(data) {
162
            if (data.checkouts) {
163
                details.checkouts = data.checkouts;
164
            }
165
            if (data.holds) {
166
                details.holds = data.holds;
167
            }
168
            display_actions(el, isbn);
169
              $("#action_"+isbn).show();
170
              $("#waiting_"+isbn).hide();
171
        });
172
    }
173
174
    function item_is_checked_out (isbn) {
175
        if ( !(details && details.checkouts) ) {
176
            return null;
177
        }
178
        var isbn_uc = isbn.toUpperCase();
179
        var items = details.checkouts.items;
180
        for (var i = 0; i < items.length; i++) {
181
            if ( items[i].isbn.toUpperCase() == isbn_uc ) {
182
                return items[i];
183
            }
184
        }
185
        return null;
186
    }
187
188
    function item_is_on_hold (isbn) {
189
        if ( !(details && details.holds) ) {
190
            return false;
191
        }
192
        var isbn_uc = isbn.toUpperCase();
193
        var items = details.holds.items;
194
        for (var i = 0; i < items.length; i++) {
195
            if ( items[i].isbn.toUpperCase() == isbn_uc ) {
196
                return items[i];
197
            }
198
        }
199
        return null;
200
    }
201
202
    function display_actions(el, isbn) {
203
        $(el).empty();
204
        if (is_identified()) {
205
206
            var item = item_is_checked_out(isbn);
207
            if (item) {
208
                var expires = new Date(item.expires);
209
                $('<span class="recordedbooks-item-status">')
210
                    .text(MSG_CHECKED_OUT_UNTIL.format(expires.toLocaleString()))
211
                    .appendTo(el);
212
                $(el).append(" ");
213
214
                if (item.url) {
215
                    var download = $('<a href="'+item.url+'">').appendTo(el);
216
                    decorate_button(download, MSG_DOWNLOAD);
217
                    $(el).append(" ");
218
                }
219
220
                $(el).append( ajax_button(MSG_CHECK_IN, function() {
221
                    if( confirm(MSG_CHECK_IN_CONFIRM) ) {
222
                        item_action({action: "return", isbn: isbn}, el);
223
                    }
224
                }) );
225
226
                return item;
227
            }
228
229
            item = item_is_on_hold(isbn);
230
            if (item) {
231
                $('<span class="recordedbooks-status">')
232
                    .text(MSG_ON_HOLD)
233
                    .appendTo(el);
234
                $(el).append(" ");
235
            }
236
237
            if(checkout_popup) {
238
                $(el).append( ajax_button(MSG_CHECK_OUT, function() {
239
                    if( confirm(MSG_CHECK_OUT_CONFIRM) ) {
240
                       $("#action_"+isbn).hide();
241
                       $("#waiting_"+isbn).show();
242
                        svc_ajax('post', {action: "checkout", isbn: isbn}, function(data) {
243
                            if (data.checkouts) {
244
                                details.checkouts = data.checkouts;
245
                            }
246
                            if (data.holds) {
247
                                details.holds = data.holds;
248
                            }
249
                            item = display_actions(el, isbn);
250
                            $("#action_"+isbn).show();
251
                            $("#waiting_"+isbn).hide();
252
                        });
253
                    }
254
                }) );
255
            }
256
            if (!item) {
257
                $(el).append( ajax_button(MSG_PLACE_HOLD, function() {
258
                    item_action({action: "place_hold", isbn: isbn}, el);
259
                }) );
260
            }
261
262
            if (item) {
263
                $(el).append( ajax_button(MSG_CANCEL_HOLD, function() {
264
                    if( confirm(MSG_CANCEL_HOLD_CONFIRM) ) {
265
                        item_action({action: "remove_hold", isbn: isbn}, el);
266
                    }
267
                }) );
268
            }
269
            return item;
270
        }
271
    }
272
273
    function ajax_button(label, on_click) {
274
        var button = $('<a href="#">')
275
            .click(function(e) {
276
                e.preventDefault();
277
                on_click();
278
            });
279
        decorate_button(button, label);
280
        return button;
281
    }
282
283
    function decorate_button(button, label) {
284
        $(button)
285
            .addClass("btn btn-primary btn-mini")
286
            .css("color","white")
287
            .text(label);
288
    }
289
290
    this.with_account_details = function( el, callback ) {
291
        $(el).append(error_div);
292
        load_account_details( callback );
293
    }
294
295
    this.display_account_details = function( el ) {
296
        $(el).empty().append(error_div);
297
        load_account_details(function(data) {
298
            display_account(el, data);
299
        });
300
    };
301
302
    this.display_error = function( el, error ) {
303
        $(el).empty().append(error_div);
304
        display_error(error);
305
    };
306
307
    this.is_identified = is_identified;
308
309
    this.add_actions = function(el, isbn) {
310
        var actions = $('<span class="actions">');
311
        display_actions(actions, isbn);
312
        $('<div id="action_'+isbn+'" class="actions-menu">')
313
            .append(actions)
314
            .appendTo(el);
315
        $("#action_"+isbn).before('<span id="waiting_'+isbn+'" style="display:none;"><img class="throbber" src="' + SPINNER_THROBBER + '" /></span>');
316
    };
317
318
    this.search = function( q, page_size, page, callback ) {
319
        svc_ajax('get', { action: "search", q: q, page_size: page_size, page: page }, function (data) {
320
            var results;
321
            if (data.results) {
322
                results = data.results;
323
                if (!results.total) {
324
                    var total = results.items.length;
325
                    if ( total == results.page_size ) total = total + "+";
326
                    results.total = total;
327
                }
328
            }
329
            else results = {};
330
            results.error = data.error;
331
            callback(results);
332
        }, true);
333
    };
334
}
(-)a/opac/opac-recordedbooks-search.pl (-43 lines)
Lines 1-43 Link Here
1
#!/usr/bin/perl
2
3
# Copyright 2013 ByWater
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20
use Modern::Perl;
21
22
use CGI qw( -utf8 );
23
24
use C4::Auth qw( get_template_and_user );
25
use C4::Output qw( output_html_with_http_headers );
26
27
my $cgi = CGI->new;
28
29
my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
30
    {
31
        template_name   => "opac-recordedbooks-search.tt",
32
        query           => $cgi,
33
        type            => "opac",
34
        authnotrequired => 1,
35
    }
36
);
37
38
$template->param(
39
    q     => scalar $cgi->param('q'),
40
    limit => C4::Context->preference('OPACnumSearchResults'),
41
);
42
43
output_html_with_http_headers $cgi, $cookie, $template->output;
(-)a/opac/opac-user.pl (-1 lines)
Lines 373-379 $template->param( Link Here
373
    OverDriveCirculation => C4::Context->preference('OverDriveCirculation') || 0,
373
    OverDriveCirculation => C4::Context->preference('OverDriveCirculation') || 0,
374
    overdrive_error      => scalar $query->param('overdrive_error') || undef,
374
    overdrive_error      => scalar $query->param('overdrive_error') || undef,
375
    overdrive_tab        => scalar $query->param('overdrive_tab') || 0,
375
    overdrive_tab        => scalar $query->param('overdrive_tab') || 0,
376
    RecordedBooksCirculation => C4::Context->preference('RecordedBooksClientSecret') && C4::Context->preference('RecordedBooksLibraryID'),
377
);
376
);
378
377
379
my $patron_messages = Koha::Patron::Messages->search(
378
my $patron_messages = Koha::Patron::Messages->search(
(-)a/opac/svc/recordedbooks (-148 lines)
Lines 1-148 Link Here
1
#!/usr/bin/perl
2
3
# Copyright 2016 Catalyst IT
4
# This file is part of Koha.
5
#
6
# Koha is free software; you can redistribute it and/or modify it
7
# under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 3 of the License, or
9
# (at your option) any later version.
10
#
11
# Koha is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with Koha; if not, see <http://www.gnu.org/licenses>.
18
19
use Modern::Perl;
20
use CGI qw ( -utf8 );
21
use JSON qw(encode_json);
22
use C4::Auth qw(checkauth);
23
use C4::Output qw( output_with_http_headers );
24
use Koha::Logger;
25
use Koha::ExternalContent::RecordedBooks;
26
27
our $cgi = CGI->new;
28
my $logger = Koha::Logger->get({ interface => 'opac' });
29
my $page_url = $cgi->referer();
30
31
my ( $user, $cookie, $sessionID, $flags ) = checkauth( $cgi, 1, {}, 'opac' );
32
33
my $action = $cgi->param('action') or response_bad_request("No 'action' specified");
34
35
($user && $sessionID) || $action eq 'search' or response_bad_request("User not logged in");
36
37
local $@;
38
my $rb = eval { Koha::ExternalContent::RecordedBooks->new({ koha_session_id => $sessionID }) };
39
unless ($rb) {
40
    $logger->error($@) if $@;
41
    response({
42
        error => $@,
43
        is_identified => JSON::false,
44
    });
45
}
46
47
my $is_identified = $rb->is_identified;
48
my %data = (
49
    is_identified => $is_identified ? JSON::true : JSON::false,
50
);
51
response(\%data) unless $is_identified || $action eq 'search';
52
53
eval {
54
    {
55
        $action eq 'search' && do {
56
            my $query = $cgi->param('q');
57
            my $page  = $cgi->param('page');
58
            my $page_size = $cgi->param('page_size');
59
            my $sort = $cgi->param('sort');
60
            my $res = $rb->search({
61
                query => $query,
62
                page  => $page,
63
                page_size => $page_size,
64
                sort => $sort,
65
            });
66
            $data{results} = $res;
67
            last;
68
        };
69
70
        $action eq 'account' && do {
71
            eval {
72
                $data{account} = $rb->patron;
73
                $data{checkouts} = $rb->checkouts;
74
                $data{holds} = $rb->holds;
75
            };
76
            response_bad_request($rb->error_message($@)) if $@;
77
            last;
78
        };
79
80
        $action eq 'checkout' && do {
81
            my $isbn = $cgi->param('isbn')
82
              or response_bad_request("No 'isbn' specified");
83
            my $format = $cgi->param('format');
84
            $data{action} = eval {  $rb->checkout($isbn, $format) };
85
            response_bad_request($rb->error_message($@)) if $@;
86
87
            eval {
88
                $data{checkouts} = $rb->checkouts;
89
                $data{holds} = $rb->holds;
90
            };
91
            $data{error} = $rb->error_message($@) if $@;
92
            last;
93
        };
94
95
        $action eq 'return' && do {
96
            my $isbn = $cgi->param('isbn')
97
              or response_bad_request("No 'isbn' specified");
98
            $data{action} = eval { $rb->return($isbn) };
99
            response_bad_request($rb->error_message($@)) if $@;
100
101
            $data{checkouts} = eval { $rb->checkouts };
102
            $data{error} = $rb->error_message($@) if $@;
103
            last;
104
        };
105
106
        $action eq 'place_hold' && do {
107
            my $isbn = $cgi->param('isbn')
108
              or response_bad_request("No 'isbn' specified");
109
            $data{action} = eval { $rb->place_hold($isbn) };
110
            response_bad_request($rb->error_message($@)) if $@;
111
112
            $data{holds} = eval { $rb->holds };
113
            $data{error} = $rb->error_message($@) if $@;
114
            last;
115
        };
116
117
        $action eq 'remove_hold' && do {
118
            my $isbn = $cgi->param('isbn')
119
              or response_bad_request("No 'isbn' specified");
120
            $data{action} = eval { $rb->remove_hold($isbn) };
121
            response_bad_request($rb->error_message($@)) if $@;
122
123
            $data{holds} = eval { $rb->holds };
124
            $data{error} = $rb->error_message($@) if $@;
125
            last;
126
        };
127
128
        response_bad_request("Invalid 'action': $action");
129
    }
130
};
131
if ($@) {
132
    $logger->error($@);
133
    $data{error} = $rb->error_message("$@");
134
}
135
136
response(\%data);
137
138
139
sub response_bad_request {
140
    my ($error) = @_;
141
    response({error => $error}, "400 $error");
142
}
143
sub response {
144
    my ($data, $status_line) = @_;
145
    $status_line ||= "200 OK";
146
    output_with_http_headers $cgi, undef, encode_json($data), 'json', $status_line;
147
    exit;
148
}
(-)a/t/db_dependent/Koha_ExternalContent_RecordedBooks.t (-56 lines)
Lines 1-55 Link Here
1
use Modern::Perl;
2
3
use t::lib::Mocks;
4
use t::lib::TestBuilder;
5
use Test::More tests => 3;                      # last test to print
6
use C4::Auth qw( get_session );
7
use Koha::Database;
8
9
use Module::Load::Conditional qw( can_load );
10
SKIP: {
11
    skip "cannot filnd WebService::ILS::RecordedBooks::PartnerPatron", 3
12
      unless can_load( modules => {'WebService::ILS::RecordedBooks::PartnerPatron' => undef} );
13
14
    use_ok('Koha::ExternalContent::RecordedBooks');
15
16
    my $ocd_user_email = $ENV{RECORDEDBOOKS_TEST_USER_EMAIL};
17
    SKIP: {
18
        skip "Env RECORDEDBOOKS_TEST_USER_EMAIL not set", 2 unless $ocd_user_email;
19
20
        my $ocd_secret = $ENV{RECORDEDBOOKS_TEST_CLIENT_SECRET}
21
          || C4::Context->preference('RecordedBooksClientSecret');
22
        my $ocd_library_id = $ENV{RECORDEDBOOKS_TEST_LIBRARY_ID}
23
          || C4::Context->preference('RecordedBooksLibraryID');
24
        my $ocd_domain = $ENV{RECORDEDBOOKS_TEST_DOMAIN}
25
          || C4::Context->preference('RecordedBooksDomain');
26
        skip "Env RECORDEDBOOKS_TEST_CLIENT_SECRET RECORDEDBOOKS_TEST_LIBRARY_ID RECORDEDBOOKS_TEST_DOMAIN not set", 2
27
          unless $ocd_secret && $ocd_library_id && $ocd_domain;
28
29
        my $schema = Koha::Database->schema;
30
        $schema->storage->txn_begin;
31
        my $builder = t::lib::TestBuilder->new();
32
33
        t::lib::Mocks::mock_preference('RecordedBooksClientSecret', $ocd_secret);
34
        t::lib::Mocks::mock_preference('RecordedBooksLibraryID', $ocd_library_id);
35
        t::lib::Mocks::mock_preference('RecordedBooksDomain', $ocd_domain);
36
37
        my $patron = $builder->build({
38
            source => 'Borrower',
39
            value => {
40
                email => $ocd_user_email,
41
            }
42
        });
43
44
        my $session = C4::Auth::get_session("");
45
        $session->param('number', $patron->{borrowernumber});
46
        $session->flush;
47
        my $client = Koha::ExternalContent::RecordedBooks->new({koha_session_id => $session->id});
48
49
        my $user_agent_string = $client->user_agent->agent();
50
        ok ($user_agent_string =~ m/^Koha/, 'User Agent string is set')
51
          or diag("User Agent string: $user_agent_string");
52
53
        ok ($client->search({query => "school"}), 'search()');
54
    }
55
}
56
- 

Return to bug 33697