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

Signed-off-by: Niamh Walker-Headon <Niamh.Walker-Headon@tudublin.ie>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
This commit is contained in:
Andrew Isherwood 2019-07-11 14:15:55 +01:00 committed by Martin Renvoize
parent a592317d86
commit f5edd39e61
No known key found for this signature in database
GPG Key ID: 422B469130441A0F
13 changed files with 861 additions and 8 deletions

View File

@ -0,0 +1,126 @@
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($params);
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. Optionally accept a hashref specifying additional filter
parameters
=cut
sub get_services {
my ( $self, $params ) = @_;
my $plugin_filter = {
method => 'ill_availability_services'
};
if ($params->{metadata}) {
$plugin_filter->{metadata} = $params->{metadata};
}
my @candidates = Koha::Plugins->new()->GetPlugins($plugin_filter);
my @services = ();
foreach my $plugin(@candidates) {
my $valid_service = $plugin->ill_availability_services({
metadata => $self->{metadata},
ui_context => $params->{ui_context}
});
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;

View File

@ -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,55 @@ 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({
ui_context => 'staff'
});
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 +284,35 @@ 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({
ui_context => 'partners',
metadata => {
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};
}

View File

@ -3759,6 +3759,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 {
@ -3795,6 +3810,10 @@ input.renew {
margin: 20px 0 30px 0;
}
.ill_availability_sourcename {
margin-top: 20px;
}
#stockrotation {
h3 {
margin: 30px 0 10px 0;

View File

@ -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>

View File

@ -291,13 +291,15 @@
<select size="5" multiple="true" id="partners" name="partners" required="required">
[% FOREACH partner IN whole.value.partners %]
[% IF partner.email && partner.email.length > 0 %]
<option value="[% partner.email | html %]">
<option data-partner-id="[% partner.id | html %]" value=[% partner.email | html %]>
[% partner.branchcode _ " - " _ partner.surname %]
</option>
[% END %]
[% 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>
@ -317,6 +319,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>
@ -689,6 +714,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 %]
@ -717,6 +776,14 @@
}).on("change", function(e, value) {
if ( ! is_valid_date( $(this).val() ) ) {$(this).val("");}
});
[% IF services_json.length > 0 %]
var services = [% services_json | $raw %];
[% ELSE %]
var services = [];
[% END %]
[% IF metadata.length > 0 %]
var metadata = "[% metadata | $raw %]";
[% END %]
</script>
<script>
$('#ill_checkout_inhouse_select').on('change', function() {
@ -729,6 +796,19 @@
</script>
[% INCLUDE 'ill-list-table-strings.inc' %]
[% Asset.js("js/ill-list-table.js") | $raw %]
[% 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 %]

View File

@ -37,6 +37,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>

View File

@ -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();
});
});

View File

@ -0,0 +1,222 @@
$(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
// Since we're not able to use the columns settings in core,
// we need to mock the object that it would return
var columns_settings = [
{
cannot_be_modified: 0,
cannot_be_toggled: 0,
columnname: 'source',
is_hidden: 0
},
{
cannot_be_modified: 0,
cannot_be_toggled: 0,
columnname: 'title',
is_hidden: 0
},
{
cannot_be_modified: 0,
cannot_be_toggled: 0,
columnname: 'author',
is_hidden: 0
},
{
cannot_be_modified: 0,
cannot_be_toggled: 0,
columnname: 'isbn',
is_hidden: 0
},
{
cannot_be_modified: 0,
cannot_be_toggled: 0,
columnname: 'issn',
is_hidden: 0
},
{
cannot_be_modified: 0,
cannot_be_toggled: 0,
columnname: 'date',
is_hidden: 0
}
];
// Hide pagination buttons if appropriate
tableDef.drawCallback = function() {
var pagination = $(this).closest('.dataTables_wrapper')
.find('.dataTables_paginate');
pagination.toggle(this.api().page.info().pages > 1);
}
KohaTable(service.id, tableDef, columns_settings);
});
}
});

View File

@ -2920,6 +2920,15 @@ button {
.dropdown:hover .dropdown-menu.nojs {
display: block;
}
}
.ill_availability_sourcename {
margin-top: 20px;
}
#continue-request-row {
text-align: center;
}
#dc_fieldset {

View File

@ -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>

View File

@ -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 %]

View File

@ -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)
);
});
}
});

View File

@ -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,46 @@ 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({
ui_context => 'opac'
});
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;