Bugzilla – Attachment 92694 Details for
Bug 23173
ILL should be able to search third party sources prior to request creation
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 23173: Provide core infrastructure
Bug-23173-Provide-core-infrastructure.patch (text/plain), 43.11 KB, created by
Andrew Isherwood
on 2019-09-10 15:33:48 UTC
(
hide
)
Description:
Bug 23173: Provide core infrastructure
Filename:
MIME Type:
Creator:
Andrew Isherwood
Created:
2019-09-10 15:33:48 UTC
Size:
43.11 KB
patch
obsolete
>From c0d8495c008731f5f2e8c97ea81dc7739ddcac91 Mon Sep 17 00:00:00 2001 >From: Andrew Isherwood <andrew.isherwood@ptfs-europe.com> >Date: Thu, 11 Jul 2019 14:15:55 +0100 >Subject: [PATCH] Bug 23173: Provide core infrastructure > >This patch adds the required infrastructure to enable ILL availability >plugins to intercept the request creation process and, using the >supplied metadata, search for and display possible relevant items from >whichever availability plugins are installed. > >Currently three availability plugins exist: > >z39.50 - Searches any number of the Koha instance's configured Z targets >https://github.com/PTFS-Europe/koha-plugin-ill-avail-z3950 > >EDS - Searches the EBSCO Discovery Service >https://github.com/PTFS-Europe/koha-plugin-ill-avail-eds > >Unpaywall - Searches the Unpaywall API for possible open access versions >of the requested item >https://github.com/PTFS-Europe/koha-plugin-ill-avail-unpaywall > >The Unpaywall plugin is intended to serve as a "reference" plugin as the >API it deals with is extremely simple >--- > Koha/Illrequest/Availability.pm | 124 ++++++++++++++ > ill/ill-requests.pl | 77 ++++++++- > .../intranet-tmpl/prog/css/src/staff-global.scss | 19 +++ > .../prog/en/includes/ill-availability-table.inc | 17 ++ > .../prog/en/modules/ill/ill-requests.tt | 84 +++++++++- > .../prog/en/modules/plugins/plugins-home.tt | 1 + > .../prog/js/ill-availability-partner.js | 24 +++ > .../intranet-tmpl/prog/js/ill-availability.js | 176 ++++++++++++++++++++ > koha-tmpl/opac-tmpl/bootstrap/css/src/opac.scss | 9 ++ > .../en/includes/ill-availability-table.inc | 17 ++ > .../bootstrap/en/modules/opac-illrequests.tt | 47 ++++++ > .../opac-tmpl/bootstrap/js/ill-availability.js | 178 +++++++++++++++++++++ > opac/opac-illrequests.pl | 41 +++++ > 13 files changed, 806 insertions(+), 8 deletions(-) > create mode 100644 Koha/Illrequest/Availability.pm > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/includes/ill-availability-table.inc > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/ill-availability-partner.js > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/ill-availability.js > create mode 100644 koha-tmpl/opac-tmpl/bootstrap/en/includes/ill-availability-table.inc > create mode 100644 koha-tmpl/opac-tmpl/bootstrap/js/ill-availability.js > >diff --git a/Koha/Illrequest/Availability.pm b/Koha/Illrequest/Availability.pm >new file mode 100644 >index 0000000000..4a4b986e85 >--- /dev/null >+++ b/Koha/Illrequest/Availability.pm >@@ -0,0 +1,124 @@ >+package Koha::Illrequest::Availability; >+ >+# Copyright 2019 PTFS Europe Ltd >+# >+# 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, write to the Free Software Foundation, Inc., >+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. >+ >+use Modern::Perl; >+ >+use JSON; >+use MIME::Base64 qw( encode_base64 ); >+use URI::Escape qw ( uri_escape ); >+ >+use Koha::Plugins; >+ >+=head1 NAME >+ >+Koha::Illrequest::Availability - Koha ILL Availability Searching >+ >+=head1 SYNOPSIS >+ >+Object-oriented class that provides availability searching via >+availability plugins >+ >+=head1 DESCRIPTION >+ >+This class provides the ability to identify and fetch API services >+that can be used to search for item availability >+ >+=head1 API >+ >+=head2 Class Methods >+ >+=head3 new >+ >+ my $availability = Koha::Illrequest::Logger->new($metadata); >+ >+Create a new Koha::Illrequest::Availability object. >+We also store the metadata to be used for searching >+ >+=cut >+ >+sub new { >+ my ( $class, $metadata ) = @_; >+ my $self = {}; >+ >+ $self->{metadata} = $metadata; >+ >+ bless $self, $class; >+ >+ return $self; >+} >+ >+=head3 get_services >+ >+ my $services = Koha::Illrequest::Availability->get_services(); >+ >+Given our metadata, iterate plugins with the right method and >+check if they can service our request and, if so, return an arrayref >+of services >+ >+=cut >+ >+sub get_services { >+ my ( $self, $metadata ) = @_; >+ >+ my $params = { >+ method => 'ill_availability_services' >+ }; >+ >+ if ($metadata) { >+ $params->{metadata} = $metadata; >+ } >+ >+ my @candidates = Koha::Plugins->new()->GetPlugins($params); >+ my @services = (); >+ foreach my $plugin(@candidates) { >+ my $valid_service = $plugin->ill_availability_services( >+ $self->{metadata} >+ ); >+ push @services, $valid_service if $valid_service; >+ } >+ >+ return \@services; >+} >+ >+=head3 prep_metadata >+ >+ my $prepared = Koha::Illrequest::Availability->prep_metadata($metadata); >+ >+Given our metadata, return a string representing that metadata that can be >+passed in a URL (encoded in JSON then Base64 encoded) >+ >+=cut >+ >+sub prep_metadata { >+ my ( $self, $metadata ) = @_; >+ >+ # We sort the metadata hashref by key before encoding it, primarily >+ # so this function returns something predictable that we can test! >+ my $json = JSON->new; >+ $json->canonical([1]); >+ return uri_escape(encode_base64($json->encode($metadata))); >+} >+ >+=head1 AUTHOR >+ >+Andrew Isherwood <andrew.isherwood@ptfs-europe.com> >+ >+=cut >+ >+1; >diff --git a/ill/ill-requests.pl b/ill/ill-requests.pl >index 010c61e618..41d0833e60 100755 >--- a/ill/ill-requests.pl >+++ b/ill/ill-requests.pl >@@ -26,11 +26,13 @@ use C4::Output; > use Koha::AuthorisedValues; > use Koha::Illcomment; > use Koha::Illrequests; >+use Koha::Illrequest::Availability; > use Koha::Libraries; > use Koha::Token; > > use Try::Tiny; > use URI::Escape; >+use JSON; > > our $cgi = CGI->new; > my $illRequests = Koha::Illrequests->new; >@@ -81,12 +83,53 @@ if ( $backends_available ) { > } elsif ( $op eq 'create' ) { > # We're in the process of creating a request > my $request = Koha::Illrequest->new->load_backend( $params->{backend} ); >- my $backend_result = $request->backend_create($params); >- $template->param( >- whole => $backend_result, >- request => $request >- ); >- handle_commit_maybe($backend_result, $request); >+ # Does this backend enable us to insert an availability stage and should >+ # we? If not, proceed as normal. >+ if ( >+ C4::Context->preference("ILLCheckAvailability") && >+ $request->_backend_capability( >+ 'should_display_availability', >+ $params >+ ) && >+ # If the user has elected to continue with the request despite >+ # having viewed availability info, this flag will be set >+ !$params->{checked_availability} >+ ) { >+ # Establish which of the installed availability providers >+ # can service our metadata >+ my $availability = Koha::Illrequest::Availability->new($params); >+ my $services = $availability->get_services(); >+ if (scalar @{$services} > 0) { >+ # Modify our method so we use the correct part of the >+ # template >+ $op = 'availability'; >+ $params->{method} = 'availability'; >+ delete $params->{stage}; >+ # Prepare the metadata we're sending them >+ my $metadata = $availability->prep_metadata($params); >+ $template->param( >+ whole => $params, >+ metadata => $metadata, >+ services_json => scalar encode_json($services), >+ services => $services >+ ); >+ } else { >+ # No services can process this metadata, so continue as normal >+ my $backend_result = $request->backend_create($params); >+ $template->param( >+ whole => $backend_result, >+ request => $request >+ ); >+ handle_commit_maybe($backend_result, $request); >+ } >+ } else { >+ my $backend_result = $request->backend_create($params); >+ $template->param( >+ whole => $backend_result, >+ request => $request >+ ); >+ handle_commit_maybe($backend_result, $request); >+ } > > } elsif ( $op eq 'migrate' ) { > # We're in the process of migrating a request >@@ -239,10 +282,32 @@ if ( $backends_available ) { > $request = Koha::Illrequests->find($params->{illrequest_id}); > $params->{current_branchcode} = C4::Context->mybranch; > $backend_result = $request->generic_confirm($params); >+ > $template->param( > whole => $backend_result, > request => $request, > ); >+ >+ # Prepare availability searching, if required >+ # Get the definition for the z39.50 plugin >+ my $availability = Koha::Illrequest::Availability->new($request->metadata); >+ my $services = $availability->get_services({ >+ name => 'ILL availability - z39.50' >+ }); >+ # Only pass availability searching stuff to the template if >+ # appropriate >+ if ( >+ C4::Context->preference('ILLCheckAvailability') && >+ scalar @{$services} > 0 >+ ) { >+ my $metadata = $availability->prep_metadata($request->metadata); >+ $template->param( metadata => $metadata ); >+ $template->param( >+ services_json => scalar encode_json($services) >+ ); >+ $template->param( services => $services ); >+ } >+ > $template->param( error => $params->{error} ) > if $params->{error}; > } >diff --git a/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss b/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss >index 09dc82b52d..a566484354 100644 >--- a/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss >+++ b/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss >@@ -3671,6 +3671,21 @@ input.renew { > top: 50%; > transform: translateY(-50%); > } >+ >+ #generic_confirm_search { >+ display: block; >+ visibility: hidden; >+ margin: 1em 0 1em 10em; >+ } >+ >+ #partnerSearch { >+ .modal-dialog { >+ width: 50vw; >+ } >+ .modal-body { >+ max-height: 70vh; >+ } >+ } > } > > .ill-view-panel { >@@ -3703,6 +3718,10 @@ input.renew { > } > } > >+.ill_availability_sourcename { >+ margin-top: 20px; >+} >+ > #stockrotation { > h3 { > margin: 30px 0 10px 0; >diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/ill-availability-table.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/ill-availability-table.inc >new file mode 100644 >index 0000000000..3cd00e709d >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/ill-availability-table.inc >@@ -0,0 +1,17 @@ >+<div> >+ <div>[% service.name %]</div> >+ <table class="ill-availability" id="[% service.id %]"> >+ <thead id="[% service.id %]-header"> >+ <tr> >+ <th>Source</th> >+ <th>Title</th> >+ <th>Author</th> >+ <th>ISBN</th> >+ <th>ISSN</th> >+ <th>Date</th> >+ </tr> >+ </thead> >+ <tbody id="[% service.id %]-body"> >+ </tbody> >+ </table> >+</div> >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/ill/ill-requests.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/ill/ill-requests.tt >index 2fa9c02017..04937adaa1 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/ill/ill-requests.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/ill/ill-requests.tt >@@ -144,12 +144,14 @@ > <label for="partners" class="required">Select partner libraries:</label> > <select size="5" multiple="true" id="partners" name="partners" required="required"> > [% FOREACH partner IN whole.value.partners %] >- <option value=[% partner.email | html %]> >+ <option data-partner-id="[% partner.id | html %]" value=[% partner.email | html %]> > [% partner.branchcode _ " - " _ partner.surname %] > </option> > [% END %] > </select> >- >+ [% IF Koha.Preference('ILLCheckAvailability') %] >+ <button type="button" id="generic_confirm_search">Search selected partners</button> >+ [% END %] > </li> > <li> > <label for="subject" class="required">Subject line:</label> >@@ -169,6 +171,29 @@ > <span><a href="[% ill_url | url %]" title="Return to request details">Cancel</a></span> > </fieldset> > </form> >+ [% IF Koha.Preference('ILLCheckAvailability') %] >+ <div id="partnerSearch" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="partnerSearchLabel" aria-hidden="true"> >+ <div class="modal-dialog"> >+ <div class="modal-content"> >+ <div class="modal-header"> >+ <button type="button" class="closebtn" data-dismiss="modal" aria-hidden="true">Ã</button> >+ <h3 id="partnerSearchLabel"> Search partners</h3> >+ </div> >+ <div class="modal-body"> >+ [% FOR service IN services %] >+ <h4 class="ill_availability_sourcename">[% service.plugin %]</h4> >+ [% INCLUDE 'ill-availability-table.inc' service=service %] >+ [% END %] >+ <span id="service_id_restrict" data-service_id_restrict_plugin="ILL availability - z39.50" data-service_id_restrict_ids=""></span> >+ </div> >+ <div class="modal-footer"> >+ <button class="btn btn-default" data-dismiss="modal" aria-hidden="true">Close</button> >+ </div> >+ </div> >+ </div> >+ </div> >+ [% END %] >+ > [% ELSE %] > <fieldset class="rows"> > <legend>Interlibrary loan request details</legend> >@@ -518,6 +543,40 @@ > [% INCLUDE 'ill-list-table.inc' %] > > </div> <!-- /#results --> >+ [% ELSIF query_type == 'availability' %] >+ <!-- availability --> >+ <h1>Availability</h1> >+ <div id="results"> >+ <h3>Displaying availability results</h3> >+ <form method="POST" action="/cgi-bin/koha/ill/ill-requests.pl"> >+ [% FOREACH key IN whole.keys %] >+ [% value = whole.$key %] >+ [% IF key != 'method' && key != 'custom_key' && key != 'custom_value' %] >+ <input type="hidden" name="[% key | html %]" value="[% value | html %]"> >+ [% END %] >+ [% END %] >+ [% custom_keys = whole.custom_key.split('\0') %] >+ [% custom_values = whole.custom_value.split('\0') %] >+ [% i = 0 %] >+ [% FOREACH custom_key IN custom_keys %] >+ <input type="hidden" name="custom_key" value="[% custom_key %]"> >+ <input type="hidden" name="custom_value" value="[% custom_values.$i %]"> >+ [% i = i + 1 %] >+ [% END %] >+ <input type="hidden" name="method" value="create"> >+ <input type="hidden" name="stage" value="form"> >+ <input type="hidden" name="checked_availability" value="1"> >+ <div id="continue-request-row" class="alert"> >+ If you can't find what you are looking for, you can >+ <button class="button" type="submit">continue creating your request</button> or >+ <a href="/cgi-bin/koha/ill/ill-requests.pl">cancel your request</a> >+ </div> >+ </form> >+ [% FOR service IN services %] >+ <h4 class="ill_availability_sourcename">[% service.plugin %]</h4> >+ [% INCLUDE 'ill-availability-table.inc' service=service %] >+ [% END %] >+ </div> > [% ELSE %] > <!-- Custom Backend Action --> > [% PROCESS $whole.template %] >@@ -539,11 +598,32 @@ > var prefilters = '[% prefilters | $raw %]'; > // Set column settings > var columns_settings = [% ColumnsSettings.GetColumns( 'illrequests', 'ill-requests', 'ill-requests', 'json' ) %]; >+ [% IF services_json.length > 0 %] >+ var services = [% services_json | $raw %]; >+ [% ELSE %] >+ var services = []; >+ [% END %] >+ [% IF metadata.length > 0 %] >+ var metadata = "[% metadata | $raw %]"; >+ [% END %] > </script> > [% IF query_type == 'illlist' %] > [% INCLUDE 'ill-list-table-strings.inc' %] > [% Asset.js("js/ill-list-table.js") | $raw %] > [% END %] >+ [% IF (query_type == 'availability' || query_type == 'generic_confirm') && Koha.Preference('ILLCheckAvailability') %] >+ [% Asset.js("js/ill-availability.js") | $raw %] >+ [% END %] >+ [% IF query_type == 'availability' && Koha.Preference('ILLCheckAvailability') %] >+ <script> >+ $(document).ready(function() { >+ window.doSearch(); >+ }); >+ </script> >+ [% END %] >+ [% IF query_type == 'generic_confirm' && Koha.Preference('ILLCheckAvailability') %] >+ [% Asset.js("js/ill-availability-partner.js") | $raw %] >+ [% END %] > [% END %] > > [% TRY %] >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/plugins/plugins-home.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/plugins/plugins-home.tt >index 5d598c525e..6c2d30acfc 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/plugins/plugins-home.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/plugins/plugins-home.tt >@@ -36,6 +36,7 @@ > <li><a href="/cgi-bin/koha/plugins/plugins-home.pl?method=to_marc">View MARC conversion plugins</a></li> > <li><a href="/cgi-bin/koha/plugins/plugins-home.pl?method=opac_online_payment">View online payment plugins</a></li> > <li><a href="/cgi-bin/koha/plugins/plugins-home.pl?method=intranet_catalog_biblio_enhancements">View intranet catalog biblio enhancement plugins</a></li> >+ <li><a href="/cgi-bin/koha/plugins/plugins-home.pl?method=ill_availability_services">View ILL availability plugins</a></li> > </ul> > </div> > </div> >diff --git a/koha-tmpl/intranet-tmpl/prog/js/ill-availability-partner.js b/koha-tmpl/intranet-tmpl/prog/js/ill-availability-partner.js >new file mode 100644 >index 0000000000..07f3664c20 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/ill-availability-partner.js >@@ -0,0 +1,24 @@ >+$(document).ready(function() { >+ $('#partners').change(function() { >+ var selected = []; >+ $('#partners option:selected').each(function() { >+ selected.push($(this).data('partner-id')); >+ }); >+ if (selected.length > 0) { >+ $('#generic_confirm_search').css('visibility', 'initial'); >+ } else { >+ $('#generic_confirm_search').css('visibility', 'hidden'); >+ } >+ $('#service_id_restrict'). >+ attr('data-service_id_restrict_ids', selected.join('|')); >+ }); >+ $('#generic_confirm_search').click(function(e) { >+ $('#partnerSearch').modal({show:true}); >+ }); >+ $('#partnerSearch').on('show.bs.modal', function() { >+ doSearch(); >+ }); >+ $('#partnerSearch').on('hide.bs.modal', function() { >+ $.fn.dataTable.tables({ api: true }).destroy(); >+ }); >+}); >diff --git a/koha-tmpl/intranet-tmpl/prog/js/ill-availability.js b/koha-tmpl/intranet-tmpl/prog/js/ill-availability.js >new file mode 100644 >index 0000000000..936734780b >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/ill-availability.js >@@ -0,0 +1,176 @@ >+$(document).ready(function() { >+ >+ window.doSearch = function() { >+ // In case the source doesn't supply data required for DT to calculate >+ // pagination, we need to do it ourselves >+ var ownPagination = false; >+ var directionSet = false; >+ var start = 0; >+ var forward = true; // true == forward, false == backwards >+ // Arbitrary starting value, it will be corrected by the first >+ // page of results >+ var pageSize = 20; >+ >+ var tableTmpl = { >+ ajax: { >+ cache: true, // Prevent DT appending a "_" cache param >+ }, >+ columns: [ >+ // defaultContent prevents DT from choking if >+ // the API response doesn't return a column >+ { >+ title: 'Source', >+ data: 'source', >+ defaultContent: '' >+ }, >+ { >+ data: 'title', >+ defaultContent: '' >+ }, >+ { >+ data: 'author', >+ defaultContent: '' >+ }, >+ { >+ data: 'isbn', >+ defaultContent: '' >+ }, >+ { >+ data: 'issn', >+ defaultContent: '' >+ }, >+ { >+ data: 'date', >+ defaultContent: '' >+ } >+ ] >+ }; >+ >+ // render functions don't get copied across when we make a dereferenced >+ // copy of them, so we have to reattach them once we have a copy >+ // Here we store them >+ var renders = { >+ title: function(data, type, row) { >+ return row.url ? >+ '<a href="'+row.url+'" target="_blank">'+row.title+'</a>' : >+ row.title; >+ }, >+ source: function(data, type, row) { >+ return row.opac_url ? >+ '<a href="'+row.opac_url+'" target="_blank">'+row.source+'</a>' : >+ row.source; >+ } >+ }; >+ >+ services.forEach(function(service) { >+ // Create a deferenced copy of our table definition object >+ var tableDef = JSON.parse(JSON.stringify(tableTmpl)); >+ // Iterate the table's columns array and add render functions >+ // as necessary >+ tableDef.columns.forEach(function(column) { >+ if (renders[column.data]) { >+ column.render = renders[column.data]; >+ } >+ }); >+ tableDef.ajax.dataSrc = function(data) { >+ var results = data.results.search_results; >+ // The source appears to be returning it's own pagination >+ // data >+ if ( >+ data.hasOwnProperty('recordsFiltered') || >+ data.hasOwnProperty('recordsTotal') >+ ) { >+ return results; >+ } >+ // Set up our own pagination values based on what we just >+ // got back >+ ownPagination = true; >+ directionSet = false; >+ pageSize = results.length; >+ // These values are completely arbitrary, but they enable >+ // us to display pagination links >+ data.recordsFiltered = 5000, >+ data.recordsTotal = 5000; >+ >+ return results; >+ }; >+ tableDef.ajax.data = function(data) { >+ // Datatables sends a bunch of superfluous params >+ // that we don't want to litter our API schema >+ // with, so just remove them from the request >+ if (data.hasOwnProperty('columns')) { >+ delete data.columns; >+ } >+ if (data.hasOwnProperty('draw')) { >+ delete data.draw; >+ } >+ if (data.hasOwnProperty('order')) { >+ delete data.order; >+ } >+ if (data.hasOwnProperty('search')) { >+ delete data.search; >+ } >+ // If we're handling our own pagination, set the properties >+ // that DT will send in the request >+ if (ownPagination) { >+ start = forward ? start + pageSize : start - pageSize; >+ data.start = start; >+ data.length = pageSize; >+ } >+ // We may need to restrict the service IDs being queries, this >+ // needs to be handled in the plugin's API module >+ var restrict = $('#service_id_restrict'). >+ attr('data-service_id_restrict_ids'); >+ if (restrict && restrict.length > 0) { >+ data.restrict = restrict; >+ } >+ }; >+ // Add any datatables config options passed from the service >+ // to the table definition >+ tableDef.ajax.url = service.endpoint + metadata; >+ if (service.hasOwnProperty('datatablesConfig')) { >+ var conf = service.datatablesConfig; >+ for (var key in conf) { >+ // The config from the service definition comes from a Perl >+ // hashref, therefore can't contain true/false, so we >+ // special case it >+ if (conf.hasOwnProperty(key)) { >+ if (conf[key] == 'false') { >+ // Special case false values >+ tableDef[key] = false; >+ } else if (conf[key] == 'true') { >+ // Special case true values >+ tableDef[key] = true; >+ } else { >+ // Copy the property value >+ tableDef[key] = conf[key]; >+ } >+ } >+ } >+ } >+ // Create event watchers for the "next" and "previous" pagination >+ // links, this enables us to set the direction the next request is >+ // going in when we're doing our own pagination. We use "hover" >+ // because the click event is caught after the request has been >+ // sent >+ tableDef.drawCallback = function() { >+ $('.paginate_button.next:not(.disabled)', >+ this.api().table().container() >+ ).on('hover', function() { >+ forward = true; >+ directionSet = true; >+ }); >+ $('.paginate_button.previous:not(.disabled)', >+ this.api().table().container() >+ ).on('hover', function() { >+ forward = false; >+ directionSet = true; >+ }); >+ } >+ // Initialise the table >+ KohaTable(service.id, tableDef); >+ }); >+ } >+ >+ >+}); >diff --git a/koha-tmpl/opac-tmpl/bootstrap/css/src/opac.scss b/koha-tmpl/opac-tmpl/bootstrap/css/src/opac.scss >index f2fea4ab62..1a64abd49d 100644 >--- a/koha-tmpl/opac-tmpl/bootstrap/css/src/opac.scss >+++ b/koha-tmpl/opac-tmpl/bootstrap/css/src/opac.scss >@@ -3038,6 +3038,15 @@ button.closebtn { > .dropdown:hover .dropdown-menu.nojs { > display: block; > } >+ >+} >+ >+.ill_availability_sourcename { >+ margin-top: 20px; >+} >+ >+#continue-request-row { >+ text-align: center; > } > > #dc_fieldset { >diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/includes/ill-availability-table.inc b/koha-tmpl/opac-tmpl/bootstrap/en/includes/ill-availability-table.inc >new file mode 100644 >index 0000000000..a2597bc3cf >--- /dev/null >+++ b/koha-tmpl/opac-tmpl/bootstrap/en/includes/ill-availability-table.inc >@@ -0,0 +1,17 @@ >+<div> >+ <div>[% service.name %]</div> >+ <table class="ill-availability table table-bordered table-striped" id="[% service.id %]"> >+ <thead id="[% service.id %]-header"> >+ <tr> >+ <th>Source</th> >+ <th>Title</th> >+ <th>Author</th> >+ <th>ISBN</th> >+ <th>ISSN</th> >+ <th>Date</th> >+ </tr> >+ </thead> >+ <tbody id="[% service.id %]-body"> >+ </tbody> >+ </table> >+</div> >diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-illrequests.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-illrequests.tt >index 218a6e2aa2..a2b0571329 100644 >--- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-illrequests.tt >+++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-illrequests.tt >@@ -224,6 +224,37 @@ > <span class="cancel"><a href="/cgi-bin/koha/opac-illrequests.pl">Cancel</a></span> > </fieldset> > </form> >+ [% ELSIF method == 'availability' %] >+ <h2>Interlibrary loan item availability</h2> >+ <div id="results"> >+ <h3>Displaying availability results</h3> >+ <form method="POST" action="/cgi-bin/koha/opac-illrequests.pl"> >+ [% FOREACH key IN whole.keys %] >+ [% value = whole.$key %] >+ [% IF key != 'custom_key' && key != 'custom_value' %] >+ <input type="hidden" name="[% key | html %]" value="[% value | html %]"> >+ [% END %] >+ [% END %] >+ [% custom_keys = whole.custom_key.split('\0') %] >+ [% custom_values = whole.custom_value.split('\0') %] >+ [% i = 0 %] >+ [% FOREACH custom_key IN custom_keys %] >+ <input type="hidden" name="custom_key" value="[% custom_key %]"> >+ <input type="hidden" name="custom_value" value="[% custom_values.$i %]"> >+ [% i = i + 1 %] >+ [% END %] >+ <input type="hidden" name="checked_availability" value="1"> >+ <div id="continue-request-row" class="alert"> >+ If you can't find what you are looking for, you can >+ <button class="button" type="submit">continue creating your request</button> or >+ <a href="/cgi-bin/koha/opac-illrequests.pl">cancel your request</a> >+ </div> >+ </form> >+ [% FOR service IN services %] >+ <h4 class="ill_availability_sourcename">[% service.plugin %]</h4> >+ [% INCLUDE 'ill-availability-table.inc' service=service %] >+ [% END %] >+ </div> > [% END %] > </div> <!-- / .maincontent --> > [% END %] >@@ -247,8 +278,24 @@ > "deferRender": true > })); > $("#backend-dropdown-options").removeClass("nojs"); >+ [% IF services_json.length > 0 %] >+ var services = [% services_json | $raw %]; >+ [% ELSE %] >+ var services = []; >+ [% END %] >+ [% IF metadata.length > 0 %] >+ var metadata = "[% metadata | $raw %]"; >+ [% END %] > //]]> > </script> >+[% IF method == 'availability' %] >+ [% Asset.js("js/ill-availability.js") | $raw %] >+ <script> >+ $(document).ready(function() { >+ window.doSearch(); >+ }) >+ </script> >+[% END %] > [% TRY %] > [% PROCESS backend_jsinclude %] > [% CATCH %] >diff --git a/koha-tmpl/opac-tmpl/bootstrap/js/ill-availability.js b/koha-tmpl/opac-tmpl/bootstrap/js/ill-availability.js >new file mode 100644 >index 0000000000..b19adc6242 >--- /dev/null >+++ b/koha-tmpl/opac-tmpl/bootstrap/js/ill-availability.js >@@ -0,0 +1,178 @@ >+$(document).ready(function() { >+ >+ window.doSearch = function() { >+ // In case the source doesn't supply data required for DT to calculate >+ // pagination, we need to do it ourselves >+ var ownPagination = false; >+ var directionSet = false; >+ var start = 0; >+ var forward = true; // true == forward, false == backwards >+ // Arbitrary starting value, it will be corrected by the first >+ // page of results >+ var pageSize = 20; >+ >+ var tableTmpl = { >+ ajax: { >+ cache: true, // Prevent DT appending a "_" cache param >+ }, >+ columns: [ >+ // defaultContent prevents DT from choking if >+ // the API response doesn't return a column >+ { >+ title: 'Source', >+ data: 'source', >+ defaultContent: '' >+ }, >+ { >+ data: 'title', >+ defaultContent: '' >+ }, >+ { >+ data: 'author', >+ defaultContent: '' >+ }, >+ { >+ data: 'isbn', >+ defaultContent: '' >+ }, >+ { >+ data: 'issn', >+ defaultContent: '' >+ }, >+ { >+ data: 'date', >+ defaultContent: '' >+ } >+ ] >+ }; >+ >+ // render functions don't get copied across when we make a dereferenced >+ // copy of them, so we have to reattach them once we have a copy >+ // Here we store them >+ var renders = { >+ title: function(data, type, row) { >+ return row.url ? >+ '<a href="'+row.url+'" target="_blank">'+row.title+'</a>' : >+ row.title; >+ }, >+ source: function(data, type, row) { >+ return row.opac_url ? >+ '<a href="'+row.opac_url+'" target="_blank">'+row.source+'</a>' : >+ row.source; >+ } >+ }; >+ >+ services.forEach(function(service) { >+ // Create a deferenced copy of our table definition object >+ var tableDef = JSON.parse(JSON.stringify(tableTmpl)); >+ // Iterate the table's columns array and add render functions >+ // as necessary >+ tableDef.columns.forEach(function(column) { >+ if (renders[column.data]) { >+ column.render = renders[column.data]; >+ } >+ }); >+ tableDef.ajax.dataSrc = function(data) { >+ var results = data.results.search_results; >+ // The source appears to be returning it's own pagination >+ // data >+ if ( >+ data.hasOwnProperty('recordsFiltered') || >+ data.hasOwnProperty('recordsTotal') >+ ) { >+ return results; >+ } >+ // Set up our own pagination values based on what we just >+ // got back >+ ownPagination = true; >+ directionSet = false; >+ pageSize = results.length; >+ // These values are completely arbitrary, but they enable >+ // us to display pagination links >+ data.recordsFiltered = 5000, >+ data.recordsTotal = 5000; >+ >+ return results; >+ }; >+ tableDef.ajax.data = function(data) { >+ // Datatables sends a bunch of superfluous params >+ // that we don't want to litter our API schema >+ // with, so just remove them from the request >+ if (data.hasOwnProperty('columns')) { >+ delete data.columns; >+ } >+ if (data.hasOwnProperty('draw')) { >+ delete data.draw; >+ } >+ if (data.hasOwnProperty('order')) { >+ delete data.order; >+ } >+ if (data.hasOwnProperty('search')) { >+ delete data.search; >+ } >+ // If we're handling our own pagination, set the properties >+ // that DT will send in the request >+ if (ownPagination) { >+ start = forward ? start + pageSize : start - pageSize; >+ data.start = start; >+ data.length = pageSize; >+ } >+ // We may need to restrict the service IDs being queries, this >+ // needs to be handled in the plugin's API module >+ var restrict = $('#service_id_restrict'). >+ attr('data-service_id_restrict_ids'); >+ if (restrict && restrict.length > 0) { >+ data.restrict = restrict; >+ } >+ }; >+ // Add any datatables config options passed from the service >+ // to the table definition >+ tableDef.ajax.url = service.endpoint + metadata; >+ if (service.hasOwnProperty('datatablesConfig')) { >+ var conf = service.datatablesConfig; >+ for (var key in conf) { >+ // The config from the service definition comes from a Perl >+ // hashref, therefore can't contain true/false, so we >+ // special case it >+ if (conf.hasOwnProperty(key)) { >+ if (conf[key] == 'false') { >+ // Special case false values >+ tableDef[key] = false; >+ } else if (conf[key] == 'true') { >+ // Special case true values >+ tableDef[key] = true; >+ } else { >+ // Copy the property value >+ tableDef[key] = conf[key]; >+ } >+ } >+ } >+ } >+ // Create event watchers for the "next" and "previous" pagination >+ // links, this enables us to set the direction the next request is >+ // going in when we're doing our own pagination. We use "hover" >+ // because the click event is caught after the request has been >+ // sent >+ tableDef.drawCallback = function() { >+ $('.paginate_button.next:not(.disabled)', >+ this.api().table().container() >+ ).on('hover', function() { >+ forward = true; >+ directionSet = true; >+ }); >+ $('.paginate_button.previous:not(.disabled)', >+ this.api().table().container() >+ ).on('hover', function() { >+ forward = false; >+ directionSet = true; >+ }); >+ } >+ // Initialise the table >+ $('#'+service.id ).dataTable( >+ $.extend(true, {}, dataTablesDefaults, tableDef) >+ ); >+ }); >+ } >+ >+ >+}); >diff --git a/opac/opac-illrequests.pl b/opac/opac-illrequests.pl >index f35b19ada9..3ceffc8460 100755 >--- a/opac/opac-illrequests.pl >+++ b/opac/opac-illrequests.pl >@@ -19,6 +19,8 @@ > > use Modern::Perl; > >+use JSON qw( encode_json ); >+ > use CGI qw ( -utf8 ); > use C4::Auth; > use C4::Koha; >@@ -28,6 +30,7 @@ use Koha::Illrequest::Config; > use Koha::Illrequests; > use Koha::Libraries; > use Koha::Patrons; >+use Koha::Illrequest::Availability; > > my $query = new CGI; > >@@ -110,6 +113,44 @@ if ( $op eq 'list' ) { > } else { > my $request = Koha::Illrequest->new > ->load_backend($params->{backend}); >+ >+ # Does this backend enable us to insert an availability stage and should >+ # we? If not, proceed as normal. >+ if ( >+ C4::Context->preference("ILLCheckAvailability") && >+ $request->_backend_capability( >+ 'should_display_availability', >+ $params >+ ) && >+ # If the user has elected to continue with the request despite >+ # having viewed availability info, this flag will be set >+ !$params->{checked_availability} >+ ) { >+ # Establish which of the installed availability providers >+ # can service our metadata, if so, jump in >+ my $availability = Koha::Illrequest::Availability->new($params); >+ my $services = $availability->get_services(); >+ if (scalar @{$services} > 0) { >+ # Modify our method so we use the correct part of the >+ # template >+ $op = 'availability'; >+ # Prepare the metadata we're sending them >+ my $metadata = $availability->prep_metadata($params); >+ $template->param( >+ metadata => $metadata, >+ services_json => encode_json($services), >+ services => $services, >+ illrequestsview => 1, >+ message => $params->{message}, >+ method => $op, >+ whole => $params >+ ); >+ output_html_with_http_headers $query, $cookie, >+ $template->output, undef, { force_no_caching => 1 }; >+ exit; >+ } >+ } >+ > $params->{cardnumber} = Koha::Patrons->find({ > borrowernumber => $loggedinuser > })->cardnumber; >-- >2.11.0
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 23173
:
91464
|
91465
|
92107
|
92108
|
92539
|
92540
|
92550
|
92551
|
92640
|
92641
|
92693
|
92694
|
92695
|
92715
|
92716
|
92717
|
93146
|
93147
|
93148
|
93909
|
93910
|
93911
|
95268
|
95269
|
95270
|
98521
|
99083
|
100724
|
100725
|
100740
|
102179
|
102180
|
102181
|
102182
|
102183
|
102184
|
102286
|
102287
|
102288
|
102289
|
102290
|
102291
|
102292
|
102293
|
102294