@@ -, +, @@ --- Koha/Illrequest/Availability.pm | 126 ++++++++++ ill/ill-requests.pl | 82 ++++++- .../prog/css/src/staff-global.scss | 19 ++ .../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 | 222 ++++++++++++++++++ .../opac-tmpl/bootstrap/css/src/opac.scss | 9 + .../en/includes/ill-availability-table.inc | 17 ++ .../bootstrap/en/modules/opac-illrequests.tt | 47 ++++ .../bootstrap/js/ill-availability.js | 178 ++++++++++++++ opac/opac-illrequests.pl | 43 ++++ 13 files changed, 861 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 --- a/Koha/Illrequest/Availability.pm +++ a/Koha/Illrequest/Availability.pm @@ -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 + +=cut + +1; --- a/ill/ill-requests.pl +++ a/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,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}; } --- a/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss +++ a/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss @@ -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; --- a/koha-tmpl/intranet-tmpl/prog/en/includes/ill-availability-table.inc +++ a/koha-tmpl/intranet-tmpl/prog/en/includes/ill-availability-table.inc @@ -0,0 +1,17 @@ +
+
[% service.name %]
+ + + + + + + + + + + + + +
SourceTitleAuthorISBNISSNDate
+
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/ill/ill-requests.tt +++ a/koha-tmpl/intranet-tmpl/prog/en/modules/ill/ill-requests.tt @@ -291,13 +291,15 @@ - + [% IF Koha.Preference('ILLCheckAvailability') %] + + [% END %]
  • @@ -317,6 +319,29 @@ Cancel + [% IF Koha.Preference('ILLCheckAvailability') %] + + [% END %] + [% ELSE %]
    Interlibrary loan request details @@ -689,6 +714,40 @@ [% INCLUDE 'ill-list-table.inc' %] + [% ELSIF query_type == 'availability' %] + +

    Availability

    +
    +

    Displaying availability results

    +
    + [% FOREACH key IN whole.keys %] + [% value = whole.$key %] + [% IF key != 'method' && key != 'custom_key' && key != 'custom_value' %] + + [% END %] + [% END %] + [% custom_keys = whole.custom_key.split('\0') %] + [% custom_values = whole.custom_value.split('\0') %] + [% i = 0 %] + [% FOREACH custom_key IN custom_keys %] + + + [% i = i + 1 %] + [% END %] + + + +
    + If you can't find what you are looking for, you can + or + cancel your request +
    +
    + [% FOR service IN services %] +

    [% service.plugin %]

    + [% INCLUDE 'ill-availability-table.inc' service=service %] + [% END %] +
    [% ELSE %] [% 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 %] [% 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') %] + + [% END %] + [% IF query_type == 'generic_confirm' && Koha.Preference('ILLCheckAvailability') %] + [% Asset.js("js/ill-availability-partner.js") | $raw %] + [% END %] [% END %] [% TRY %] --- a/koha-tmpl/intranet-tmpl/prog/en/modules/plugins/plugins-home.tt +++ a/koha-tmpl/intranet-tmpl/prog/en/modules/plugins/plugins-home.tt @@ -37,6 +37,7 @@
  • View MARC conversion plugins
  • View online payment plugins
  • View intranet catalog biblio enhancement plugins
  • +
  • View ILL availability plugins
  • --- a/koha-tmpl/intranet-tmpl/prog/js/ill-availability-partner.js +++ a/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(); + }); +}); --- a/koha-tmpl/intranet-tmpl/prog/js/ill-availability.js +++ a/koha-tmpl/intranet-tmpl/prog/js/ill-availability.js @@ -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 ? + ''+row.title+'' : + row.title; + }, + source: function(data, type, row) { + return row.opac_url ? + ''+row.source+'' : + 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); + }); + } + + +}); --- a/koha-tmpl/opac-tmpl/bootstrap/css/src/opac.scss +++ a/koha-tmpl/opac-tmpl/bootstrap/css/src/opac.scss @@ -2919,6 +2919,15 @@ button { .dropdown:hover .dropdown-menu.nojs { display: block; } + +} + +.ill_availability_sourcename { + margin-top: 20px; +} + +#continue-request-row { + text-align: center; } #dc_fieldset { --- a/koha-tmpl/opac-tmpl/bootstrap/en/includes/ill-availability-table.inc +++ a/koha-tmpl/opac-tmpl/bootstrap/en/includes/ill-availability-table.inc @@ -0,0 +1,17 @@ +
    +
    [% service.name %]
    + + + + + + + + + + + + + +
    SourceTitleAuthorISBNISSNDate
    +
    --- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-illrequests.tt +++ a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-illrequests.tt @@ -224,6 +224,37 @@ Cancel
    + [% ELSIF method == 'availability' %] +

    Interlibrary loan item availability

    +
    +

    Displaying availability results

    +
    + [% FOREACH key IN whole.keys %] + [% value = whole.$key %] + [% IF key != 'custom_key' && key != 'custom_value' %] + + [% END %] + [% END %] + [% custom_keys = whole.custom_key.split('\0') %] + [% custom_values = whole.custom_value.split('\0') %] + [% i = 0 %] + [% FOREACH custom_key IN custom_keys %] + + + [% i = i + 1 %] + [% END %] + +
    + If you can't find what you are looking for, you can + or + cancel your request +
    +
    + [% FOR service IN services %] +

    [% service.plugin %]

    + [% INCLUDE 'ill-availability-table.inc' service=service %] + [% END %] +
    [% END %] [% 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 %] //]]> +[% IF method == 'availability' %] + [% Asset.js("js/ill-availability.js") | $raw %] + +[% END %] [% TRY %] [% PROCESS backend_jsinclude %] [% CATCH %] --- a/koha-tmpl/opac-tmpl/bootstrap/js/ill-availability.js +++ a/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 ? + ''+row.title+'' : + row.title; + }, + source: function(data, type, row) { + return row.opac_url ? + ''+row.source+'' : + 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) + ); + }); + } + + +}); --- a/opac/opac-illrequests.pl +++ a/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,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; --