Bug 33697: Remove RecordedBooks (rbdigital) integration
RecordedBooks search API integration is now obsolete following rbdigital's incorporation into OverDrive. Associated code should be removed. https://company.overdrive.com/2020/06/23/overdrive-to-acquire-rbdigital-from-rbmedia/ Test plan: use git grep extensively and confirm that this patch removes all occurrences of this feature. Signed-off-by: Owen Leonard <oleonard@myacpl.org> Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl> Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
This commit is contained in:
parent
229b6b58ef
commit
8b9355088a
15 changed files with 16 additions and 960 deletions
|
@ -1,119 +0,0 @@
|
|||
# Copyright 2016 Catalyst
|
||||
#
|
||||
# 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 3 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, see <http://www.gnu.org/licenses>.
|
||||
|
||||
package Koha::ExternalContent::RecordedBooks;
|
||||
|
||||
use Modern::Perl;
|
||||
use Carp qw( croak );
|
||||
|
||||
use base qw(Koha::ExternalContent);
|
||||
use WebService::ILS::RecordedBooks::PartnerPatron;
|
||||
use WebService::ILS::RecordedBooks::Partner;
|
||||
use C4::Context;
|
||||
|
||||
__PACKAGE__->mk_accessors(qw(domain is_identified));
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Koha::ExternalContent::RecordedBooks
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Koha::ExternalContent::RecordedBooks;
|
||||
my $rb_client = Koha::ExternalContent::RecordedBooks->new();
|
||||
my $rb_auth_url = $od_client->auth_url();
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
A (very) thin wrapper around C<WebService::ILS::RecordedBooks::Patron>
|
||||
|
||||
Takes "RecordedBooks*" Koha preferences
|
||||
|
||||
=cut
|
||||
|
||||
=head2 Class Methods
|
||||
|
||||
=cut
|
||||
|
||||
=head3 new
|
||||
|
||||
my $rb_client = Koha::ExternalContent::RecordedBooks->new();
|
||||
|
||||
Create the object for interacting with RecordedBooks
|
||||
|
||||
=cut
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $params = shift || {};
|
||||
|
||||
my $self = $class->SUPER::new($params);
|
||||
unless ($params->{client}) {
|
||||
my $client_secret = C4::Context->preference('RecordedBooksClientSecret')
|
||||
or croak("RecordedBooksClientSecret pref not set");
|
||||
my $library_id = C4::Context->preference('RecordedBooksLibraryID')
|
||||
or croak("RecordedBooksLibraryID pref not set");
|
||||
my $domain = C4::Context->preference('RecordedBooksDomain');
|
||||
my $patron = $params->{koha_session_id} ? $self->koha_patron : undef;
|
||||
my $email;
|
||||
if ($patron) {
|
||||
$email = $patron->email
|
||||
or $self->logger->warn("User with no email, cannot identify with RecordedBooks");
|
||||
}
|
||||
my $client;
|
||||
if ($email) {
|
||||
local $@;
|
||||
$client = eval { WebService::ILS::RecordedBooks::PartnerPatron->new(
|
||||
client_secret => $client_secret,
|
||||
library_id => $library_id,
|
||||
domain => $domain,
|
||||
user_id => $email,
|
||||
) };
|
||||
$self->logger->warn("Invalid RecordedBooks user $email ($@)") if $@;
|
||||
$self->is_identified($client);
|
||||
}
|
||||
$client ||= WebService::ILS::RecordedBooks::Partner->new(
|
||||
client_secret => $client_secret,
|
||||
library_id => $library_id,
|
||||
domain => $domain,
|
||||
);
|
||||
$self->client( $client );
|
||||
}
|
||||
return $self;
|
||||
}
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
L<WebService::ILS::RecordedBooks::PartnerPatron> methods used without mods:
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<error_message()>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
use vars qw{$AUTOLOAD};
|
||||
sub AUTOLOAD {
|
||||
my $self = shift;
|
||||
(my $method = $AUTOLOAD) =~ s/.*:://;
|
||||
return $self->client->$method(@_);
|
||||
}
|
||||
sub DESTROY { }
|
||||
|
||||
1;
|
16
installer/data/mysql/atomicupdate/bug_33697.pl
Executable file
16
installer/data/mysql/atomicupdate/bug_33697.pl
Executable file
|
@ -0,0 +1,16 @@
|
|||
use Modern::Perl;
|
||||
|
||||
return {
|
||||
bug_number => "33697",
|
||||
description => "Remove RecordedBooks (rbdigital) integration",
|
||||
up => sub {
|
||||
my ($args) = @_;
|
||||
my ($dbh, $out) = @$args{qw(dbh out)};
|
||||
for my $pref_name ( qw( RecordedBooksClientSecret RecordedBooksDomain RecordedBooksLibraryID ) ) {
|
||||
$dbh->do(q{
|
||||
DELETE FROM systempreferences
|
||||
WHERE variable=?
|
||||
}, undef, $pref_name) == 1 && say $out "Removed system preference '$pref_name'";
|
||||
}
|
||||
},
|
||||
};
|
|
@ -303,9 +303,6 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, `
|
|||
('IndependentBranchesTransfers','0', NULL, 'Allow non-superlibrarians to transfer items between libraries','YesNo'),
|
||||
('IntranetAddMastheadLibraryPulldown','0', NULL, 'Add a library select pulldown menu on the staff header search','YesNo'),
|
||||
('IntranetCatalogSearchPulldown','0', NULL, 'Show a search field pulldown for \"Search the catalog\" boxes','YesNo'),
|
||||
('RecordedBooksClientSecret','','30','Client key for RecordedBooks integration','Free'),
|
||||
('RecordedBooksDomain','','','RecordedBooks domain','Free'),
|
||||
('RecordedBooksLibraryID','','','Library ID for RecordedBooks integration','Integer'),
|
||||
('OnSiteCheckouts','0','','Enable/Disable the on-site checkouts feature','YesNo'),
|
||||
('OnSiteCheckoutsForce','0','','Enable/Disable the on-site for all cases (Even if a user is debarred, etc.)','YesNo'),
|
||||
('OnSiteCheckoutAutoCheck','0','','Enable/Do not enable onsite checkout by default if last checkout was an onsite checkout','YesNo'),
|
||||
|
|
|
@ -377,18 +377,6 @@ Enhanced content:
|
|||
- for user access to OverDrive. <br />
|
||||
- If you enable access you must have a SIP connection registered with
|
||||
- OverDrive for patron authentication against Koha
|
||||
RecordedBooks:
|
||||
-
|
||||
- Include RecordedBooks availability information with the client secret
|
||||
- pref: RecordedBooksClientSecret
|
||||
- .
|
||||
-
|
||||
- Show items from the RecordedBooks catalog of library ID
|
||||
- pref: RecordedBooksLibraryID
|
||||
- .
|
||||
-
|
||||
- RecordedBooks domain
|
||||
- pref: RecordedBooksDomain
|
||||
Coce cover images cache:
|
||||
-
|
||||
- pref: OpacCoce
|
||||
|
|
|
@ -2276,7 +2276,6 @@ nav {
|
|||
}
|
||||
|
||||
#overdrive-results,
|
||||
#recordedbooks-results,
|
||||
#openlibrary-results {
|
||||
font-weight: bold;
|
||||
padding-left: 1em;
|
||||
|
|
|
@ -272,12 +272,6 @@
|
|||
</script>
|
||||
[% END %]
|
||||
|
||||
[% IF Koha.Preference('RecordedBooksClientSecret') && Koha.Preference('RecordedBooksLibraryID') %]
|
||||
<script>
|
||||
var SPINNER_THROBBER = "[% interface | html %]/[% theme | html %]/images/spinner-small.gif";
|
||||
</script>
|
||||
[% END %]
|
||||
|
||||
[% Asset.js("lib/js-cookie/js.cookie-3.0.1.min.js") | $raw %]
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
<div id="recordedbooks-checkout" class="modal" tabindex="-1" role="dialog" aria-labelledby="recordedbooks-checkout-label" aria-hidden="true">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h3 id="recordedbooks-checkout-label">Checkout</h3>
|
||||
</div>
|
||||
<form action="#" method="post" id="recordedbooks-checkout-form">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="id" value="" />
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<input type="submit" class="btn btn-default recordedbooks-checkout-submit" value="Checkout" />
|
||||
<a href="#" data-dismiss="modal" aria-hidden="true" class="cancel">Cancel</a>
|
||||
</div>
|
||||
</form> <!-- /#recordedbooks-checkout-form -->
|
||||
</div> <!-- /#recordedbooks-checkout -->
|
|
@ -1,178 +0,0 @@
|
|||
[% USE raw %]
|
||||
[% USE Asset %]
|
||||
[% USE Koha %]
|
||||
[% USE AdditionalContents %]
|
||||
[% SET OpacNav = AdditionalContents.get( location => "OpacNav", lang => lang, library => logged_in_user.branchcode || default_branch, blocktitle => 0 ) %]
|
||||
[% SET OpacNavBottom = AdditionalContents.get( location => "OpacNavBottom", lang => lang, library => logged_in_user.branchcode || default_branch, blocktitle => 0 ) %]
|
||||
[% INCLUDE 'doc-head-open.inc' %]
|
||||
<title>RecordedBooks search for '[% q | html %]' › [% IF ( LibraryNameTitle ) %][% LibraryNameTitle | html %][% ELSE %]Koha online[% END %] catalog</title>
|
||||
[% INCLUDE 'doc-head-close.inc' %]
|
||||
[% BLOCK cssinclude %]
|
||||
[% END %]
|
||||
</head>
|
||||
[% INCLUDE 'bodytag.inc' bodyid='recordedbooks-results-page' bodyclass='scrollto' %]
|
||||
[% INCLUDE 'masthead.inc' %]
|
||||
|
||||
<div class="main">
|
||||
<nav id="breadcrumbs" aria-label="Breadcrumb" class="breadcrumbs">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item">
|
||||
<a href="/cgi-bin/koha/opac-main.pl">Home</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item active">
|
||||
<a href="#" aria-current="page">RecordedBooks search for '[% q | html %]'</a>
|
||||
</li>
|
||||
</ol>
|
||||
</nav> <!-- /#breadcrumbs -->
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-lg-2">
|
||||
[% IF ( OpacNav || OpacNavBottom ) %]
|
||||
[% INCLUDE 'navigation.inc' %]
|
||||
[% END %]
|
||||
</div>
|
||||
<div class="col-lg-10 order-first order-md-first order-lg-2">
|
||||
<div id="recordedbooks-results-content" class="maincontent searchresults">
|
||||
<h1>RecordedBooks search for '[% q | html %]'</h1>
|
||||
[% UNLESS ( logged_in_user ) %]
|
||||
<h2 class="rb_login">Sign in to view availability and checkout items or place holds</h2>
|
||||
[% END %]
|
||||
|
||||
<div id="breadcrumbs">
|
||||
<p></p>
|
||||
</div>
|
||||
|
||||
<div id="top-pages">
|
||||
<nav class="pagination pagination-sm noprint" aria-label="Search results pagination">
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<table id="recordedbooks-results-list" class="table table-striped">
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div id="bottom-pages">
|
||||
<nav class="pagination pagination-sm noprint" aria-label="Search results pagination">
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
</div> <!-- / #recordedbooks-results-content -->
|
||||
</div> <!-- / .col-lg-10 -->
|
||||
</div> <!-- / .row -->
|
||||
</div> <!-- / .container-fluid -->
|
||||
</div> <!-- / .main -->
|
||||
|
||||
[% INCLUDE 'recordedbooks-checkout.inc' %]
|
||||
|
||||
[% INCLUDE 'opac-bottom.inc' %]
|
||||
[% BLOCK jsinclude %]
|
||||
[% Asset.js("js/recordedbooks.js") | $raw %]
|
||||
<script>
|
||||
var querystring = "[% q |replace( "'", "\'" ) |replace( '\n', '\\n' ) |replace( '\r', '\\r' ) |html %]";
|
||||
var results_per_page = [% OPACnumSearchResults || 20 | html %];
|
||||
|
||||
function search( page ) {
|
||||
$( '#recordedbooks-status' ).html( MSG_SEARCHING.format("RecordedBooks") + ' <img class="throbber" src="[% interface | html %]/[% theme | html %]/images/spinner-small.gif" /></span>' );
|
||||
|
||||
KOHA.RecordedBooks.search( querystring, results_per_page, page, function( data ) {
|
||||
if ( data.error ) {
|
||||
$( '#recordedbooks-status' ).html( '<strong class="unavailable">' + MSG_ERROR_SEARCHING_COLLECTION.format("RecordedBooks") + ': ' + data.error + '</strong>' );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( !data.total ) {
|
||||
$( '#recordedbooks-status' ).html( '<strong>' + MSG_NO_RESULTS_FOUND_IN_COLLECTION.format("RecordedBooks") + '</strong>' );
|
||||
return;
|
||||
}
|
||||
|
||||
$( '#recordedbooks-results-list tbody' ).empty();
|
||||
|
||||
$( '#recordedbooks-status' ).html( '<strong>' + MSG_RESULTS_FOUND_IN_COLLECTION.format(data.total, "RecordedBooks") + '</strong>' );
|
||||
|
||||
for ( var i = 0; data.items[i]; i++ ) {
|
||||
var prod = data.items[i];
|
||||
var results = [];
|
||||
results.push( '<tr>' );
|
||||
|
||||
results.push( '<td class="info"><span class="title">' );
|
||||
if (prod.url) results.push( '<a href="', prod.url, '" target="recordedbooks">' );
|
||||
results.push( prod.title );
|
||||
if (prod.url) results.push( '</a>' );
|
||||
results.push( '</span>' );
|
||||
results.push( '<p>' + MSG_BY + ' ', prod.author, '</p>' );
|
||||
if (prod.description) results.push( '<p>' + prod.description, '</p>' );
|
||||
results.push( '<span class="results_summary mediatype"><span class="label">' + MSG_TYPE + ': </span>', prod.media, '</span>' );
|
||||
|
||||
results.push( '</td>' );
|
||||
|
||||
results.push( '<td>' );
|
||||
if ( prod.images && prod.images.medium ) {
|
||||
if (prod.url) results.push( '<a href="', prod.url, '" target="recordedbooks">' );
|
||||
results.push( '<img class="thumbnail" src="', prod.images.medium, '" />' );
|
||||
if (prod.url) results.push( '</a>' );
|
||||
}
|
||||
results.push( '</td>' );
|
||||
|
||||
results.push( '</tr>' );
|
||||
var $tr = $( results.join( '' ));
|
||||
$( '#recordedbooks-results-list tbody' ).append( $tr );
|
||||
|
||||
$tr.find( '.info' ).each(function() {
|
||||
KOHA.RecordedBooks.add_actions(this, prod.isbn);
|
||||
});
|
||||
}
|
||||
|
||||
$( '#recordedbooks-results-list tr:odd' ).addClass( 'highlight' );
|
||||
|
||||
var pages = [];
|
||||
|
||||
var max_page = Math.floor( data.total / results_per_page );
|
||||
if (data.total == page*results_per_page) max_page++;
|
||||
|
||||
if ( page != 1 ) {
|
||||
pages.push( '<li class="page-item"><a class="page-link od-nav" href="#" data-page="' + (page - 1) + '">« ' + MSG_PREVIOUS + '</a></li>' );
|
||||
}
|
||||
|
||||
for ( var p = Math.max( 0, page - 9 ); p <= Math.min( max_page, p + 9 ); p++ ) {
|
||||
if ( p == page ) {
|
||||
pages.push( ' <li class="page-item disabled"><a class="page-link" href="#">' + ( p + 1 ) + '</a></li>' );
|
||||
} else {
|
||||
pages.push( ' <li class="page-item"><a class="page-link od-nav" href="#" data-page="' + p + '">' + p + '</a></li>' );
|
||||
}
|
||||
}
|
||||
|
||||
if ( page < max_page ) {
|
||||
pages.push( ' <li class="page-item"><a class="page-link od-nav" href="#" data-page="' + (page + 1) + '">' + MSG_NEXT + ' »</a></li>' );
|
||||
}
|
||||
|
||||
if ( pages.length > 1 ) $( '#top-pages, #bottom-pages' ).find( '.pagination' ).html( '<ul class="pagination">' + pages.join( '' ) + '</ul>');
|
||||
|
||||
if( KOHA.RecordedBooks.is_identified() == false ){
|
||||
$("#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>');
|
||||
}
|
||||
|
||||
} );
|
||||
}
|
||||
|
||||
$( document ).ready( function() {
|
||||
$( '#breadcrumbs p' )
|
||||
.append( ' ' )
|
||||
.append( '<span id="recordedbooks-status"></span>' );
|
||||
|
||||
$( document ).on( 'click', 'a.od-nav', function() {
|
||||
search( $( this ).data( 'page' ) );
|
||||
return false;
|
||||
});
|
||||
|
||||
[% IF ( logged_in_user ) %]
|
||||
KOHA.RecordedBooks.with_account_details("#breadcrumbs", function() {
|
||||
search( 1 );
|
||||
});
|
||||
[% ELSE %]
|
||||
search( 1 );
|
||||
[% END %]
|
||||
} );
|
||||
</script>
|
||||
[% END %]
|
|
@ -9,7 +9,6 @@
|
|||
|
||||
[% IF firstPage %]
|
||||
[% SET OverDriveEnabled = Koha.Preference('OverDriveLibraryID') && Koha.Preference('OverDriveClientKey') && Koha.Preference('OverDriveClientSecret') %]
|
||||
[% SET RecordedBooksEnabled = Koha.Preference('RecordedBooksLibraryID') && Koha.Preference('RecordedBooksClientSecret') %]
|
||||
[% END %]
|
||||
|
||||
[% INCLUDE 'doc-head-open.inc' %]
|
||||
|
@ -582,7 +581,6 @@
|
|||
<script src="https://ltfl.librarything.com/forlibraries/widget.js?id=[% LibraryThingForLibrariesID | html %]&systype=koha"></script>
|
||||
[% END %]
|
||||
[% IF ( OverDriveEnabled ) %][% Asset.js("js/overdrive.js") | $raw %][% END %]
|
||||
[% IF ( RecordedBooksEnabled ) %][% Asset.js("js/recordedbooks.js") | $raw %][% END %]
|
||||
[% Asset.js("js/authtoresults.js") | $raw %]
|
||||
[% Asset.js("lib/hc-sticky.js") | $raw %]
|
||||
[% IF ( OpacHighlightedWords ) %]
|
||||
|
@ -836,23 +834,6 @@
|
|||
}
|
||||
} );
|
||||
[% END # /IF OverDriveEnabled %]
|
||||
[% IF ( RecordedBooksEnabled ) %]
|
||||
var $recordedbooks_results = $( '<div id="recordedbooks-results">' + MSG_SEARCHING.format('RecordedBooks') + ' <img class="throbber" src="[% interface | html %]/[% theme | html %]/images/spinner-small.gif" /></div>' );
|
||||
$( '#numresults' ) .after( $recordedbooks_results );
|
||||
KOHA.RecordedBooks.search( querystring, [% OPACnumSearchResults || "null" | html %], null, function( data ) {
|
||||
if ( data.error ) {
|
||||
$recordedbooks_results.html( MSG_ERROR_SEARCHING_COLLECTION.format('RecordedBooks') + ': ' + data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
// data.total can be either 42 or "60+"
|
||||
if ( typeof(data.total) === 'string' && data.total.charAt(0) > 0 || typeof(data.total) === 'number' && data.total > 0 ) {
|
||||
$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>' );
|
||||
} else {
|
||||
$recordedbooks_results.remove();
|
||||
}
|
||||
} );
|
||||
[% END # /IF RecordedBooksEnabled %]
|
||||
[% IF ( OpenLibrarySearch ) %]
|
||||
var $openlibrary_results = $( '<div id="openlibrary-results">' + MSG_SEARCHING.format('OpenLibrary' ) + ' <img class="throbber" src="[% interface | html %]/[% theme | html %]/images/spinner-small.gif" /></div>' );
|
||||
$( '#numresults' ) .after( $openlibrary_results );
|
||||
|
|
|
@ -312,18 +312,11 @@
|
|||
<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>
|
||||
</li>
|
||||
[% END %]
|
||||
[% IF ( RecordedBooksCirculation ) %]
|
||||
<li class="nav-item" role="presentation">
|
||||
<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>
|
||||
</li>
|
||||
[% END %]
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
|
||||
<div id="opac-user-overdrive" class="tab-pane" role="tabpanel" aria-labelledby="opac-user-overdrive-tab">
|
||||
</div>
|
||||
<div id="opac-user-recordedbooks" class="tab-pane" role="tabpanel" aria-labelledby="opac-user-recordedbooks-tab">
|
||||
</div>
|
||||
<div id="opac-user-checkouts" class="tab-pane active" role="tabpanel" aria-labelledby="opac-user-checkouts-tab">
|
||||
[% IF ( issues_count ) %]
|
||||
<form id="renewselected" action="/cgi-bin/koha/opac-renew.pl" method="post">
|
||||
|
@ -1070,9 +1063,6 @@
|
|||
[% INCLUDE 'overdrive-login.inc' %]
|
||||
[% END %]
|
||||
[% END %]
|
||||
[% IF ( RecordedBooksCirculation ) %]
|
||||
[% INCLUDE 'recordedbooks-checkout.inc' %]
|
||||
[% END %]
|
||||
|
||||
[% INCLUDE 'opac-bottom.inc' %]
|
||||
[% BLOCK jsinclude %]
|
||||
|
@ -1437,20 +1427,4 @@
|
|||
});
|
||||
</script>
|
||||
[% END %]
|
||||
[% IF RecordedBooksCirculation %]
|
||||
[% Asset.js("js/recordedbooks.js") | $raw %]
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
[% IF ( recordedbooks_error ) %]
|
||||
KOHA.RecordedBooks.display_error("#opac-user-recordedbooks", "[% recordedbooks_error.dquote | html %]");
|
||||
[% END %]
|
||||
[% IF ( recordedbooks_tab ) %]
|
||||
$("#opac-user-views a[href='#opac-user-recordedbooks']").tab("show");
|
||||
[% END %]
|
||||
$("#opac-user-recordedbooks").each( function() {
|
||||
KOHA.RecordedBooks.display_account_details(this);
|
||||
} );
|
||||
});
|
||||
</script>
|
||||
[% END %]
|
||||
[% END %]
|
||||
|
|
|
@ -1,334 +0,0 @@
|
|||
if ( typeof KOHA == "undefined" || !KOHA ) {
|
||||
var KOHA = {};
|
||||
}
|
||||
|
||||
KOHA.RecordedBooks = new function() {
|
||||
var svc_url = '/cgi-bin/koha/svc/recordedbooks';
|
||||
|
||||
var error_div = $('<div class="recordedbooks-error">');
|
||||
function display_error ( error ) {
|
||||
error_div.text(error);
|
||||
}
|
||||
|
||||
var details = null;
|
||||
|
||||
function is_identified() {
|
||||
return details ? details.is_identified : false;
|
||||
}
|
||||
|
||||
var checkout_popup = null;
|
||||
$( document ).ready(function() {
|
||||
checkout_popup = $("#recordedbooks-checkout");
|
||||
});
|
||||
|
||||
function display_account (container, data) {
|
||||
if (!data.is_identified) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.checkouts) {
|
||||
var checkouts_div = $('<div class="recordedbooks-div">').html('<h3>' + MSG_CHECKOUTS + '</h3>');
|
||||
var items = data.checkouts.items;
|
||||
var checkouts_list;
|
||||
if (items.length == 0) {
|
||||
checkouts_list = MSG_NO_CHECKOUTS;
|
||||
} else {
|
||||
checkouts_list = $('<ul class="recordedbooks-list">');
|
||||
data.checkouts.items.forEach(function(item) {
|
||||
item_line(checkouts_list, item);
|
||||
});
|
||||
}
|
||||
checkouts_div.append(checkouts_list);
|
||||
$(container).append(checkouts_div);
|
||||
}
|
||||
|
||||
if (data.holds) {
|
||||
var holds_div = $('<div class="recordedbooks-div">').html('<h3>' + MSG_HOLDS + '</h3>');
|
||||
var items = data.holds.items;
|
||||
var holds_list;
|
||||
if (items.length == 0) {
|
||||
holds_list = MSG_NO_HOLDS;
|
||||
} else {
|
||||
holds_list = $('<ul class="recordedbooks-list">');
|
||||
data.holds.items.forEach(function(item) {
|
||||
item_line(holds_list, item);
|
||||
});
|
||||
}
|
||||
holds_div.append(holds_list);
|
||||
$(container).append(holds_div);
|
||||
}
|
||||
}
|
||||
|
||||
function item_line(ul_el, item) {
|
||||
var line = $('<li class="recordedbooks-item">');
|
||||
if (item.images) {
|
||||
var thumb_url = item.images.small;
|
||||
if (thumb_url) {
|
||||
$('<img class="recordedbooks-item-thumbnail">')
|
||||
.attr("src", thumb_url)
|
||||
.appendTo(line);
|
||||
}
|
||||
}
|
||||
$('<div class="recordedbooks-item-title">')
|
||||
.text(item.title)
|
||||
.appendTo(line);
|
||||
$('<div class="recordedbooks-item-subtitle">')
|
||||
.text(item.subtitle)
|
||||
.appendTo(line);
|
||||
$('<div class="recordedbooks-item-author">')
|
||||
.text(item.author)
|
||||
.appendTo(line);
|
||||
if (item.files && item.files.length > 0) {
|
||||
downloads = $('<div class="recordedbooks-item-author">')
|
||||
.text("Downloads")
|
||||
.appendTo(line);
|
||||
render_downloads(downloads, item.files);
|
||||
}
|
||||
var actions = $('<span class="actions">');
|
||||
display_actions(actions, item.isbn);
|
||||
$('<div id="action_'+item.isbn+'" class="actions-menu">')
|
||||
.append(actions)
|
||||
.appendTo(line);
|
||||
$('<span id="waiting_'+item.isbn+'" style="display:none;"><img class="throbber" src="' + SPINNER_THROBBER + '" /></span>').appendTo(line);
|
||||
$(ul_el).append(line);
|
||||
}
|
||||
|
||||
function render_downloads(el, files) {
|
||||
if (files.length == 0) return;
|
||||
var file_spec = files.shift();
|
||||
if (/^https?:\/\/api\./.test(file_spec.url)) {
|
||||
$.ajax({
|
||||
dataType: "json",
|
||||
url: file_spec.url,
|
||||
success: function (data) {
|
||||
append_download_link(el, data.url, data.id);
|
||||
render_downloads(el, files);
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
display_error(errorThrown);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
append_download_link(el, file_spec.url, file_spec.filename);
|
||||
render_downloads(el, files);
|
||||
}
|
||||
}
|
||||
function append_download_link(el, url, text) {
|
||||
var p = $("<p>");
|
||||
$( '<a href="' + url + '" target="recordedbooks">' )
|
||||
.text(text)
|
||||
.appendTo(p);
|
||||
el.append(p);
|
||||
}
|
||||
|
||||
function svc_ajax ( method, params, success_callback, callback_for_error_too ) {
|
||||
// remove when jquery is upgraded
|
||||
for (var key in params) {
|
||||
if (params[key] === null) delete params[key];
|
||||
}
|
||||
return $.ajax({
|
||||
method: method,
|
||||
dataType: "json",
|
||||
url: svc_url,
|
||||
data: params,
|
||||
success: function (data) {
|
||||
if (data.error && !callback_for_error_too) {
|
||||
display_error(data.error);
|
||||
}
|
||||
success_callback(data);
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
if (callback_for_error_too) {
|
||||
success_callback({error: errorThrown});
|
||||
return;
|
||||
}
|
||||
display_error(errorThrown);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function load_account_details ( callback ) {
|
||||
svc_ajax('get', { action: "account" }, function(data) {
|
||||
details = data;
|
||||
callback(data);
|
||||
});
|
||||
}
|
||||
|
||||
function item_action (params, el) {
|
||||
var isbn = params.isbn;
|
||||
$("#action_"+isbn).hide();
|
||||
$("#waiting_"+isbn).show();
|
||||
svc_ajax('post', params, function(data) {
|
||||
if (data.checkouts) {
|
||||
details.checkouts = data.checkouts;
|
||||
}
|
||||
if (data.holds) {
|
||||
details.holds = data.holds;
|
||||
}
|
||||
display_actions(el, isbn);
|
||||
$("#action_"+isbn).show();
|
||||
$("#waiting_"+isbn).hide();
|
||||
});
|
||||
}
|
||||
|
||||
function item_is_checked_out (isbn) {
|
||||
if ( !(details && details.checkouts) ) {
|
||||
return null;
|
||||
}
|
||||
var isbn_uc = isbn.toUpperCase();
|
||||
var items = details.checkouts.items;
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
if ( items[i].isbn.toUpperCase() == isbn_uc ) {
|
||||
return items[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function item_is_on_hold (isbn) {
|
||||
if ( !(details && details.holds) ) {
|
||||
return false;
|
||||
}
|
||||
var isbn_uc = isbn.toUpperCase();
|
||||
var items = details.holds.items;
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
if ( items[i].isbn.toUpperCase() == isbn_uc ) {
|
||||
return items[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function display_actions(el, isbn) {
|
||||
$(el).empty();
|
||||
if (is_identified()) {
|
||||
|
||||
var item = item_is_checked_out(isbn);
|
||||
if (item) {
|
||||
var expires = new Date(item.expires);
|
||||
$('<span class="recordedbooks-item-status">')
|
||||
.text(MSG_CHECKED_OUT_UNTIL.format(expires.toLocaleString()))
|
||||
.appendTo(el);
|
||||
$(el).append(" ");
|
||||
|
||||
if (item.url) {
|
||||
var download = $('<a href="'+item.url+'">').appendTo(el);
|
||||
decorate_button(download, MSG_DOWNLOAD);
|
||||
$(el).append(" ");
|
||||
}
|
||||
|
||||
$(el).append( ajax_button(MSG_CHECK_IN, function() {
|
||||
if( confirm(MSG_CHECK_IN_CONFIRM) ) {
|
||||
item_action({action: "return", isbn: isbn}, el);
|
||||
}
|
||||
}) );
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
item = item_is_on_hold(isbn);
|
||||
if (item) {
|
||||
$('<span class="recordedbooks-status">')
|
||||
.text(MSG_ON_HOLD)
|
||||
.appendTo(el);
|
||||
$(el).append(" ");
|
||||
}
|
||||
|
||||
if(checkout_popup) {
|
||||
$(el).append( ajax_button(MSG_CHECK_OUT, function() {
|
||||
if( confirm(MSG_CHECK_OUT_CONFIRM) ) {
|
||||
$("#action_"+isbn).hide();
|
||||
$("#waiting_"+isbn).show();
|
||||
svc_ajax('post', {action: "checkout", isbn: isbn}, function(data) {
|
||||
if (data.checkouts) {
|
||||
details.checkouts = data.checkouts;
|
||||
}
|
||||
if (data.holds) {
|
||||
details.holds = data.holds;
|
||||
}
|
||||
item = display_actions(el, isbn);
|
||||
$("#action_"+isbn).show();
|
||||
$("#waiting_"+isbn).hide();
|
||||
});
|
||||
}
|
||||
}) );
|
||||
}
|
||||
if (!item) {
|
||||
$(el).append( ajax_button(MSG_PLACE_HOLD, function() {
|
||||
item_action({action: "place_hold", isbn: isbn}, el);
|
||||
}) );
|
||||
}
|
||||
|
||||
if (item) {
|
||||
$(el).append( ajax_button(MSG_CANCEL_HOLD, function() {
|
||||
if( confirm(MSG_CANCEL_HOLD_CONFIRM) ) {
|
||||
item_action({action: "remove_hold", isbn: isbn}, el);
|
||||
}
|
||||
}) );
|
||||
}
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
function ajax_button(label, on_click) {
|
||||
var button = $('<a href="#">')
|
||||
.click(function(e) {
|
||||
e.preventDefault();
|
||||
on_click();
|
||||
});
|
||||
decorate_button(button, label);
|
||||
return button;
|
||||
}
|
||||
|
||||
function decorate_button(button, label) {
|
||||
$(button)
|
||||
.addClass("btn btn-primary btn-mini")
|
||||
.css("color","white")
|
||||
.text(label);
|
||||
}
|
||||
|
||||
this.with_account_details = function( el, callback ) {
|
||||
$(el).append(error_div);
|
||||
load_account_details( callback );
|
||||
}
|
||||
|
||||
this.display_account_details = function( el ) {
|
||||
$(el).empty().append(error_div);
|
||||
load_account_details(function(data) {
|
||||
display_account(el, data);
|
||||
});
|
||||
};
|
||||
|
||||
this.display_error = function( el, error ) {
|
||||
$(el).empty().append(error_div);
|
||||
display_error(error);
|
||||
};
|
||||
|
||||
this.is_identified = is_identified;
|
||||
|
||||
this.add_actions = function(el, isbn) {
|
||||
var actions = $('<span class="actions">');
|
||||
display_actions(actions, isbn);
|
||||
$('<div id="action_'+isbn+'" class="actions-menu">')
|
||||
.append(actions)
|
||||
.appendTo(el);
|
||||
$("#action_"+isbn).before('<span id="waiting_'+isbn+'" style="display:none;"><img class="throbber" src="' + SPINNER_THROBBER + '" /></span>');
|
||||
};
|
||||
|
||||
this.search = function( q, page_size, page, callback ) {
|
||||
svc_ajax('get', { action: "search", q: q, page_size: page_size, page: page }, function (data) {
|
||||
var results;
|
||||
if (data.results) {
|
||||
results = data.results;
|
||||
if (!results.total) {
|
||||
var total = results.items.length;
|
||||
if ( total == results.page_size ) total = total + "+";
|
||||
results.total = total;
|
||||
}
|
||||
}
|
||||
else results = {};
|
||||
results.error = data.error;
|
||||
callback(results);
|
||||
}, true);
|
||||
};
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
#!/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 3 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, see <http://www.gnu.org/licenses>.
|
||||
|
||||
use Modern::Perl;
|
||||
|
||||
use CGI qw( -utf8 );
|
||||
|
||||
use C4::Auth qw( get_template_and_user );
|
||||
use C4::Output qw( output_html_with_http_headers );
|
||||
|
||||
my $cgi = CGI->new;
|
||||
|
||||
my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
|
||||
{
|
||||
template_name => "opac-recordedbooks-search.tt",
|
||||
query => $cgi,
|
||||
type => "opac",
|
||||
authnotrequired => 1,
|
||||
}
|
||||
);
|
||||
|
||||
$template->param(
|
||||
q => scalar $cgi->param('q'),
|
||||
limit => C4::Context->preference('OPACnumSearchResults'),
|
||||
);
|
||||
|
||||
output_html_with_http_headers $cgi, $cookie, $template->output;
|
|
@ -373,7 +373,6 @@ $template->param(
|
|||
OverDriveCirculation => C4::Context->preference('OverDriveCirculation') || 0,
|
||||
overdrive_error => scalar $query->param('overdrive_error') || undef,
|
||||
overdrive_tab => scalar $query->param('overdrive_tab') || 0,
|
||||
RecordedBooksCirculation => C4::Context->preference('RecordedBooksClientSecret') && C4::Context->preference('RecordedBooksLibraryID'),
|
||||
);
|
||||
|
||||
my $patron_messages = Koha::Patron::Messages->search(
|
||||
|
|
|
@ -1,148 +0,0 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
# Copyright 2016 Catalyst IT
|
||||
# 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 3 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, see <http://www.gnu.org/licenses>.
|
||||
|
||||
use Modern::Perl;
|
||||
use CGI qw ( -utf8 );
|
||||
use JSON qw(encode_json);
|
||||
use C4::Auth qw(checkauth);
|
||||
use C4::Output qw( output_with_http_headers );
|
||||
use Koha::Logger;
|
||||
use Koha::ExternalContent::RecordedBooks;
|
||||
|
||||
our $cgi = CGI->new;
|
||||
my $logger = Koha::Logger->get({ interface => 'opac' });
|
||||
my $page_url = $cgi->referer();
|
||||
|
||||
my ( $user, $cookie, $sessionID, $flags ) = checkauth( $cgi, 1, {}, 'opac' );
|
||||
|
||||
my $action = $cgi->param('action') or response_bad_request("No 'action' specified");
|
||||
|
||||
($user && $sessionID) || $action eq 'search' or response_bad_request("User not logged in");
|
||||
|
||||
local $@;
|
||||
my $rb = eval { Koha::ExternalContent::RecordedBooks->new({ koha_session_id => $sessionID }) };
|
||||
unless ($rb) {
|
||||
$logger->error($@) if $@;
|
||||
response({
|
||||
error => $@,
|
||||
is_identified => JSON::false,
|
||||
});
|
||||
}
|
||||
|
||||
my $is_identified = $rb->is_identified;
|
||||
my %data = (
|
||||
is_identified => $is_identified ? JSON::true : JSON::false,
|
||||
);
|
||||
response(\%data) unless $is_identified || $action eq 'search';
|
||||
|
||||
eval {
|
||||
{
|
||||
$action eq 'search' && do {
|
||||
my $query = $cgi->param('q');
|
||||
my $page = $cgi->param('page');
|
||||
my $page_size = $cgi->param('page_size');
|
||||
my $sort = $cgi->param('sort');
|
||||
my $res = $rb->search({
|
||||
query => $query,
|
||||
page => $page,
|
||||
page_size => $page_size,
|
||||
sort => $sort,
|
||||
});
|
||||
$data{results} = $res;
|
||||
last;
|
||||
};
|
||||
|
||||
$action eq 'account' && do {
|
||||
eval {
|
||||
$data{account} = $rb->patron;
|
||||
$data{checkouts} = $rb->checkouts;
|
||||
$data{holds} = $rb->holds;
|
||||
};
|
||||
response_bad_request($rb->error_message($@)) if $@;
|
||||
last;
|
||||
};
|
||||
|
||||
$action eq 'checkout' && do {
|
||||
my $isbn = $cgi->param('isbn')
|
||||
or response_bad_request("No 'isbn' specified");
|
||||
my $format = $cgi->param('format');
|
||||
$data{action} = eval { $rb->checkout($isbn, $format) };
|
||||
response_bad_request($rb->error_message($@)) if $@;
|
||||
|
||||
eval {
|
||||
$data{checkouts} = $rb->checkouts;
|
||||
$data{holds} = $rb->holds;
|
||||
};
|
||||
$data{error} = $rb->error_message($@) if $@;
|
||||
last;
|
||||
};
|
||||
|
||||
$action eq 'return' && do {
|
||||
my $isbn = $cgi->param('isbn')
|
||||
or response_bad_request("No 'isbn' specified");
|
||||
$data{action} = eval { $rb->return($isbn) };
|
||||
response_bad_request($rb->error_message($@)) if $@;
|
||||
|
||||
$data{checkouts} = eval { $rb->checkouts };
|
||||
$data{error} = $rb->error_message($@) if $@;
|
||||
last;
|
||||
};
|
||||
|
||||
$action eq 'place_hold' && do {
|
||||
my $isbn = $cgi->param('isbn')
|
||||
or response_bad_request("No 'isbn' specified");
|
||||
$data{action} = eval { $rb->place_hold($isbn) };
|
||||
response_bad_request($rb->error_message($@)) if $@;
|
||||
|
||||
$data{holds} = eval { $rb->holds };
|
||||
$data{error} = $rb->error_message($@) if $@;
|
||||
last;
|
||||
};
|
||||
|
||||
$action eq 'remove_hold' && do {
|
||||
my $isbn = $cgi->param('isbn')
|
||||
or response_bad_request("No 'isbn' specified");
|
||||
$data{action} = eval { $rb->remove_hold($isbn) };
|
||||
response_bad_request($rb->error_message($@)) if $@;
|
||||
|
||||
$data{holds} = eval { $rb->holds };
|
||||
$data{error} = $rb->error_message($@) if $@;
|
||||
last;
|
||||
};
|
||||
|
||||
response_bad_request("Invalid 'action': $action");
|
||||
}
|
||||
};
|
||||
if ($@) {
|
||||
$logger->error($@);
|
||||
$data{error} = $rb->error_message("$@");
|
||||
}
|
||||
|
||||
response(\%data);
|
||||
|
||||
|
||||
sub response_bad_request {
|
||||
my ($error) = @_;
|
||||
response({error => $error}, "400 $error");
|
||||
}
|
||||
sub response {
|
||||
my ($data, $status_line) = @_;
|
||||
$status_line ||= "200 OK";
|
||||
output_with_http_headers $cgi, undef, encode_json($data), 'json', $status_line;
|
||||
exit;
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
use Modern::Perl;
|
||||
|
||||
use t::lib::Mocks;
|
||||
use t::lib::TestBuilder;
|
||||
use Test::More tests => 3; # last test to print
|
||||
use C4::Auth qw( get_session );
|
||||
use Koha::Database;
|
||||
|
||||
use Module::Load::Conditional qw( can_load );
|
||||
SKIP: {
|
||||
skip "cannot filnd WebService::ILS::RecordedBooks::PartnerPatron", 3
|
||||
unless can_load( modules => {'WebService::ILS::RecordedBooks::PartnerPatron' => undef} );
|
||||
|
||||
use_ok('Koha::ExternalContent::RecordedBooks');
|
||||
|
||||
my $ocd_user_email = $ENV{RECORDEDBOOKS_TEST_USER_EMAIL};
|
||||
SKIP: {
|
||||
skip "Env RECORDEDBOOKS_TEST_USER_EMAIL not set", 2 unless $ocd_user_email;
|
||||
|
||||
my $ocd_secret = $ENV{RECORDEDBOOKS_TEST_CLIENT_SECRET}
|
||||
|| C4::Context->preference('RecordedBooksClientSecret');
|
||||
my $ocd_library_id = $ENV{RECORDEDBOOKS_TEST_LIBRARY_ID}
|
||||
|| C4::Context->preference('RecordedBooksLibraryID');
|
||||
my $ocd_domain = $ENV{RECORDEDBOOKS_TEST_DOMAIN}
|
||||
|| C4::Context->preference('RecordedBooksDomain');
|
||||
skip "Env RECORDEDBOOKS_TEST_CLIENT_SECRET RECORDEDBOOKS_TEST_LIBRARY_ID RECORDEDBOOKS_TEST_DOMAIN not set", 2
|
||||
unless $ocd_secret && $ocd_library_id && $ocd_domain;
|
||||
|
||||
my $schema = Koha::Database->schema;
|
||||
$schema->storage->txn_begin;
|
||||
my $builder = t::lib::TestBuilder->new();
|
||||
|
||||
t::lib::Mocks::mock_preference('RecordedBooksClientSecret', $ocd_secret);
|
||||
t::lib::Mocks::mock_preference('RecordedBooksLibraryID', $ocd_library_id);
|
||||
t::lib::Mocks::mock_preference('RecordedBooksDomain', $ocd_domain);
|
||||
|
||||
my $patron = $builder->build({
|
||||
source => 'Borrower',
|
||||
value => {
|
||||
email => $ocd_user_email,
|
||||
}
|
||||
});
|
||||
|
||||
my $session = C4::Auth::get_session("");
|
||||
$session->param('number', $patron->{borrowernumber});
|
||||
$session->flush;
|
||||
my $client = Koha::ExternalContent::RecordedBooks->new({koha_session_id => $session->id});
|
||||
|
||||
my $user_agent_string = $client->user_agent->agent();
|
||||
ok ($user_agent_string =~ m/^Koha/, 'User Agent string is set')
|
||||
or diag("User Agent string: $user_agent_string");
|
||||
|
||||
ok ($client->search({query => "school"}), 'search()');
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue