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

(-)a/Koha/Illrequest/Availability.pm (+126 lines)
Line 0 Link Here
1
package Koha::Illrequest::Availability;
2
3
# Copyright 2019 PTFS Europe Ltd
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it under the
8
# terms of the GNU General Public License as published by the Free Software
9
# Foundation; either version 3 of the License, or (at your option) any later
10
# version.
11
#
12
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License along
17
# with Koha; if not, write to the Free Software Foundation, Inc.,
18
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20
use Modern::Perl;
21
22
use JSON;
23
use MIME::Base64 qw( encode_base64 );
24
use URI::Escape qw ( uri_escape );
25
26
use Koha::Plugins;
27
28
=head1 NAME
29
30
Koha::Illrequest::Availability - Koha ILL Availability Searching
31
32
=head1 SYNOPSIS
33
34
Object-oriented class that provides availability searching via
35
availability plugins
36
37
=head1 DESCRIPTION
38
39
This class provides the ability to identify and fetch API services
40
that can be used to search for item availability
41
42
=head1 API
43
44
=head2 Class Methods
45
46
=head3 new
47
48
    my $availability = Koha::Illrequest::Logger->new($metadata);
49
50
Create a new Koha::Illrequest::Availability object.
51
We also store the metadata to be used for searching
52
53
=cut
54
55
sub new {
56
    my ( $class, $metadata ) = @_;
57
    my $self  = {};
58
59
    $self->{metadata} = $metadata;
60
61
    bless $self, $class;
62
63
    return $self;
64
}
65
66
=head3 get_services
67
68
    my $services = Koha::Illrequest::Availability->get_services($params);
69
70
Given our metadata, iterate plugins with the right method and
71
check if they can service our request and, if so, return an arrayref
72
of services. Optionally accept a hashref specifying additional filter
73
parameters
74
75
=cut
76
77
sub get_services {
78
    my ( $self, $params ) = @_;
79
80
    my $plugin_filter = {
81
        method => 'ill_availability_services'
82
    };
83
84
    if ($params->{metadata}) {
85
        $plugin_filter->{metadata} = $params->{metadata};
86
    }
87
88
    my @candidates = Koha::Plugins->new()->GetPlugins($plugin_filter);
89
    my @services = ();
90
    foreach my $plugin(@candidates) {
91
        my $valid_service = $plugin->ill_availability_services({
92
            metadata => $self->{metadata},
93
            ui_context => $params->{ui_context}
94
        });
95
        push @services, $valid_service if $valid_service;
96
    }
97
98
    return \@services;
99
}
100
101
=head3 prep_metadata
102
103
    my $prepared = Koha::Illrequest::Availability->prep_metadata($metadata);
104
105
Given our metadata, return a string representing that metadata that can be
106
passed in a URL (encoded in JSON then Base64 encoded)
107
108
=cut
109
110
sub prep_metadata {
111
    my ( $self, $metadata ) = @_;
112
113
    # We sort the metadata hashref by key before encoding it, primarily
114
    # so this function returns something predictable that we can test!
115
    my $json = JSON->new;
116
    $json->canonical([1]);
117
    return uri_escape(encode_base64($json->encode($metadata)));
118
}
119
120
=head1 AUTHOR
121
122
Andrew Isherwood <andrew.isherwood@ptfs-europe.com>
123
124
=cut
125
126
1;
(-)a/ill/ill-requests.pl (-6 / +76 lines)
Lines 26-36 use C4::Output; Link Here
26
use Koha::AuthorisedValues;
26
use Koha::AuthorisedValues;
27
use Koha::Illcomment;
27
use Koha::Illcomment;
28
use Koha::Illrequests;
28
use Koha::Illrequests;
29
use Koha::Illrequest::Availability;
29
use Koha::Libraries;
30
use Koha::Libraries;
30
use Koha::Token;
31
use Koha::Token;
31
32
32
use Try::Tiny;
33
use Try::Tiny;
33
use URI::Escape;
34
use URI::Escape;
35
use JSON;
34
36
35
our $cgi = CGI->new;
37
our $cgi = CGI->new;
36
my $illRequests = Koha::Illrequests->new;
38
my $illRequests = Koha::Illrequests->new;
Lines 81-92 if ( $backends_available ) { Link Here
81
    } elsif ( $op eq 'create' ) {
83
    } elsif ( $op eq 'create' ) {
82
        # We're in the process of creating a request
84
        # We're in the process of creating a request
83
        my $request = Koha::Illrequest->new->load_backend( $params->{backend} );
85
        my $request = Koha::Illrequest->new->load_backend( $params->{backend} );
84
        my $backend_result = $request->backend_create($params);
86
        # Does this backend enable us to insert an availability stage and should
85
        $template->param(
87
        # we? If not, proceed as normal.
86
            whole   => $backend_result,
88
        if (
87
            request => $request
89
            C4::Context->preference("ILLCheckAvailability") &&
88
        );
90
            $request->_backend_capability(
89
        handle_commit_maybe($backend_result, $request);
91
                'should_display_availability',
92
                $params
93
            ) &&
94
            # If the user has elected to continue with the request despite
95
            # having viewed availability info, this flag will be set
96
            !$params->{checked_availability}
97
        ) {
98
            # Establish which of the installed availability providers
99
            # can service our metadata
100
            my $availability = Koha::Illrequest::Availability->new($params);
101
            my $services = $availability->get_services({
102
                ui_context => 'staff'
103
            });
104
            if (scalar @{$services} > 0) {
105
                # Modify our method so we use the correct part of the
106
                # template
107
                $op = 'availability';
108
                $params->{method} = 'availability';
109
                delete $params->{stage};
110
                # Prepare the metadata we're sending them
111
                my $metadata = $availability->prep_metadata($params);
112
                $template->param(
113
                    whole         => $params,
114
                    metadata      => $metadata,
115
                    services_json => scalar encode_json($services),
116
                    services      => $services
117
                );
118
            } else {
119
                # No services can process this metadata, so continue as normal
120
                my $backend_result = $request->backend_create($params);
121
                $template->param(
122
                    whole   => $backend_result,
123
                    request => $request
124
                );
125
                handle_commit_maybe($backend_result, $request);
126
            }
127
        } else {
128
            my $backend_result = $request->backend_create($params);
129
            $template->param(
130
                whole   => $backend_result,
131
                request => $request
132
            );
133
            handle_commit_maybe($backend_result, $request);
134
        }
90
135
91
    } elsif ( $op eq 'migrate' ) {
136
    } elsif ( $op eq 'migrate' ) {
92
        # We're in the process of migrating a request
137
        # We're in the process of migrating a request
Lines 239-248 if ( $backends_available ) { Link Here
239
            $request = Koha::Illrequests->find($params->{illrequest_id});
284
            $request = Koha::Illrequests->find($params->{illrequest_id});
240
            $params->{current_branchcode} = C4::Context->mybranch;
285
            $params->{current_branchcode} = C4::Context->mybranch;
241
            $backend_result = $request->generic_confirm($params);
286
            $backend_result = $request->generic_confirm($params);
287
242
            $template->param(
288
            $template->param(
243
                whole => $backend_result,
289
                whole => $backend_result,
244
                request => $request,
290
                request => $request,
245
            );
291
            );
292
293
            # Prepare availability searching, if required
294
            # Get the definition for the z39.50 plugin
295
            my $availability = Koha::Illrequest::Availability->new($request->metadata);
296
            my $services = $availability->get_services({
297
                ui_context => 'partners',
298
                metadata => {
299
                    name => 'ILL availability - z39.50'
300
                }
301
            });
302
            # Only pass availability searching stuff to the template if
303
            # appropriate
304
            if (
305
                C4::Context->preference('ILLCheckAvailability') &&
306
                scalar @{$services} > 0
307
            ) {
308
                my $metadata = $availability->prep_metadata($request->metadata);
309
                $template->param( metadata => $metadata );
310
                $template->param(
311
                    services_json => scalar encode_json($services)
312
                );
313
                $template->param( services => $services );
314
            }
315
246
            $template->param( error => $params->{error} )
316
            $template->param( error => $params->{error} )
247
                if $params->{error};
317
                if $params->{error};
248
        }
318
        }
(-)a/koha-tmpl/intranet-tmpl/prog/css/src/staff-global.scss (+19 lines)
Lines 3759-3764 input.renew { Link Here
3759
        top: 50%;
3759
        top: 50%;
3760
        transform: translateY(-50%);
3760
        transform: translateY(-50%);
3761
    }
3761
    }
3762
3763
    #generic_confirm_search {
3764
        display: block;
3765
        visibility: hidden;
3766
        margin: 1em 0 1em 10em;
3767
    }
3768
3769
    #partnerSearch {
3770
        .modal-dialog {
3771
            width: 50vw;
3772
        }
3773
        .modal-body {
3774
            max-height: 70vh;
3775
        }
3776
    }
3762
}
3777
}
3763
3778
3764
.ill-view-panel {
3779
.ill-view-panel {
Lines 3795-3800 input.renew { Link Here
3795
    margin: 20px 0 30px 0;
3810
    margin: 20px 0 30px 0;
3796
}
3811
}
3797
3812
3813
.ill_availability_sourcename {
3814
    margin-top: 20px;
3815
}
3816
3798
#stockrotation {
3817
#stockrotation {
3799
    h3 {
3818
    h3 {
3800
        margin: 30px 0 10px 0;
3819
        margin: 30px 0 10px 0;
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/ill-availability-table.inc (+17 lines)
Line 0 Link Here
1
<div>
2
    <div>[% service.name %]</div>
3
    <table class="ill-availability" id="[% service.id %]">
4
        <thead id="[% service.id %]-header">
5
            <tr>
6
                <th>Source</th>
7
                <th>Title</th>
8
                <th>Author</th>
9
                <th>ISBN</th>
10
                <th>ISSN</th>
11
                <th>Date</th>
12
            </tr>
13
        </thead>
14
        <tbody id="[% service.id %]-body">
15
        </tbody>
16
    </table>
17
</div>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/ill/ill-requests.tt (-2 / +82 lines)
Lines 291-303 Link Here
291
                                        <select size="5" multiple="true" id="partners" name="partners" required="required">
291
                                        <select size="5" multiple="true" id="partners" name="partners" required="required">
292
                                            [% FOREACH partner IN whole.value.partners %]
292
                                            [% FOREACH partner IN whole.value.partners %]
293
                                                [% IF partner.email && partner.email.length > 0 %]
293
                                                [% IF partner.email && partner.email.length > 0 %]
294
                                                    <option value="[% partner.email | html %]">
294
                                                    <option data-partner-id="[% partner.id | html %]" value=[% partner.email | html %]>
295
                                                        [% partner.branchcode _ " - " _ partner.surname %]
295
                                                        [% partner.branchcode _ " - " _ partner.surname %]
296
                                                    </option>
296
                                                    </option>
297
                                                [% END %]
297
                                                [% END %]
298
                                            [% END %]
298
                                            [% END %]
299
                                        </select>
299
                                        </select>
300
300
                                        [% IF Koha.Preference('ILLCheckAvailability') %]
301
                                            <button type="button" id="generic_confirm_search">Search selected partners</button>
302
                                        [% END %]
301
                                    </li>
303
                                    </li>
302
                                    <li>
304
                                    <li>
303
                                        <label for="subject" class="required">Subject line:</label>
305
                                        <label for="subject" class="required">Subject line:</label>
Lines 317-322 Link Here
317
                                <span><a href="[% ill_url | url %]" title="Return to request details">Cancel</a></span>
319
                                <span><a href="[% ill_url | url %]" title="Return to request details">Cancel</a></span>
318
                            </fieldset>
320
                            </fieldset>
319
                        </form>
321
                        </form>
322
                        [% IF Koha.Preference('ILLCheckAvailability') %]
323
                            <div id="partnerSearch" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="partnerSearchLabel" aria-hidden="true">
324
                                <div class="modal-dialog">
325
                                    <div class="modal-content">
326
                                        <div class="modal-header">
327
                                            <button type="button" class="closebtn" data-dismiss="modal" aria-hidden="true">×</button>
328
                                            <h3 id="partnerSearchLabel"> Search partners</h3>
329
                                        </div>
330
                                        <div class="modal-body">
331
                                            [% FOR service IN services %]
332
                                                <h4 class="ill_availability_sourcename">[% service.plugin %]</h4>
333
                                                [% INCLUDE 'ill-availability-table.inc' service=service %]
334
                                            [% END %]
335
                                            <span id="service_id_restrict" data-service_id_restrict_plugin="ILL availability - z39.50" data-service_id_restrict_ids=""></span>
336
                                        </div>
337
                                        <div class="modal-footer">
338
                                            <button class="btn btn-default" data-dismiss="modal" aria-hidden="true">Close</button>
339
                                        </div>
340
                                    </div>
341
                                </div>
342
                            </div>
343
                        [% END %]
344
320
                    [% ELSE %]
345
                    [% ELSE %]
321
                        <fieldset class="rows">
346
                        <fieldset class="rows">
322
                            <legend>Interlibrary loan request details</legend>
347
                            <legend>Interlibrary loan request details</legend>
Lines 689-694 Link Here
689
                        [% INCLUDE 'ill-list-table.inc' %]
714
                        [% INCLUDE 'ill-list-table.inc' %]
690
715
691
                    </div> <!-- /#results -->
716
                    </div> <!-- /#results -->
717
                [% ELSIF query_type == 'availability' %]
718
                    <!-- availability -->
719
                    <h1>Availability</h1>
720
                    <div id="results">
721
                        <h3>Displaying availability results</h3>
722
                        <form method="POST" action="/cgi-bin/koha/ill/ill-requests.pl">
723
                            [% FOREACH key IN whole.keys %]
724
                                [% value = whole.$key %]
725
                                [% IF key != 'method' && key != 'custom_key' && key != 'custom_value' %]
726
                                <input type="hidden" name="[% key | html %]" value="[% value | html %]">
727
                                [% END %]
728
                            [% END %]
729
                            [% custom_keys = whole.custom_key.split('\0') %]
730
                            [% custom_values = whole.custom_value.split('\0') %]
731
                            [% i = 0 %]
732
                            [% FOREACH custom_key IN custom_keys %]
733
                                <input type="hidden" name="custom_key" value="[% custom_key %]">
734
                                <input type="hidden" name="custom_value" value="[% custom_values.$i %]">
735
                            [% i = i + 1 %]
736
                            [% END %]
737
                            <input type="hidden" name="method" value="create">
738
                            <input type="hidden" name="stage" value="form">
739
                            <input type="hidden" name="checked_availability" value="1">
740
                            <div id="continue-request-row" class="alert">
741
                                If you can't find what you are looking for, you can
742
                                <button class="button" type="submit">continue creating your request</button> or
743
                                <a href="/cgi-bin/koha/ill/ill-requests.pl">cancel your request</a>
744
                            </div>
745
                        </form>
746
                        [% FOR service IN services %]
747
                            <h4 class="ill_availability_sourcename">[% service.plugin %]</h4>
748
                            [% INCLUDE 'ill-availability-table.inc' service=service %]
749
                        [% END %]
750
                    </div>
692
                [% ELSE %]
751
                [% ELSE %]
693
                <!-- Custom Backend Action -->
752
                <!-- Custom Backend Action -->
694
                [% PROCESS $whole.template %]
753
                [% PROCESS $whole.template %]
Lines 717-722 Link Here
717
        }).on("change", function(e, value) {
776
        }).on("change", function(e, value) {
718
            if ( ! is_valid_date( $(this).val() ) ) {$(this).val("");}
777
            if ( ! is_valid_date( $(this).val() ) ) {$(this).val("");}
719
        });
778
        });
779
        [% IF services_json.length > 0 %]
780
        var services = [% services_json | $raw %];
781
        [% ELSE %]
782
        var services = [];
783
        [% END %]
784
        [% IF metadata.length > 0 %]
785
        var metadata = "[% metadata | $raw %]";
786
        [% END %]
720
    </script>
787
    </script>
721
    <script>
788
    <script>
722
        $('#ill_checkout_inhouse_select').on('change', function() {
789
        $('#ill_checkout_inhouse_select').on('change', function() {
Lines 729-734 Link Here
729
    </script>
796
    </script>
730
    [% INCLUDE 'ill-list-table-strings.inc' %]
797
    [% INCLUDE 'ill-list-table-strings.inc' %]
731
    [% Asset.js("js/ill-list-table.js") | $raw %]
798
    [% Asset.js("js/ill-list-table.js") | $raw %]
799
    [% IF (query_type == 'availability' || query_type == 'generic_confirm') && Koha.Preference('ILLCheckAvailability') %]
800
        [% Asset.js("js/ill-availability.js") | $raw %]
801
    [% END %]
802
    [% IF query_type == 'availability' && Koha.Preference('ILLCheckAvailability') %]
803
        <script>
804
            $(document).ready(function() {
805
                window.doSearch();
806
            });
807
        </script>
808
    [% END %]
809
    [% IF query_type == 'generic_confirm' && Koha.Preference('ILLCheckAvailability') %]
810
        [% Asset.js("js/ill-availability-partner.js") | $raw %]
811
    [% END %]
732
[% END %]
812
[% END %]
733
813
734
[% TRY %]
814
[% TRY %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/plugins/plugins-home.tt (+1 lines)
Lines 37-42 Link Here
37
                                    <li><a href="/cgi-bin/koha/plugins/plugins-home.pl?method=to_marc">View MARC conversion plugins</a></li>
37
                                    <li><a href="/cgi-bin/koha/plugins/plugins-home.pl?method=to_marc">View MARC conversion plugins</a></li>
38
                                    <li><a href="/cgi-bin/koha/plugins/plugins-home.pl?method=opac_online_payment">View online payment plugins</a></li>
38
                                    <li><a href="/cgi-bin/koha/plugins/plugins-home.pl?method=opac_online_payment">View online payment plugins</a></li>
39
                                    <li><a href="/cgi-bin/koha/plugins/plugins-home.pl?method=intranet_catalog_biblio_enhancements">View intranet catalog biblio enhancement plugins</a></li>
39
                                    <li><a href="/cgi-bin/koha/plugins/plugins-home.pl?method=intranet_catalog_biblio_enhancements">View intranet catalog biblio enhancement plugins</a></li>
40
                                    <li><a href="/cgi-bin/koha/plugins/plugins-home.pl?method=ill_availability_services">View ILL availability plugins</a></li>
40
                                </ul>
41
                                </ul>
41
                            </div>
42
                            </div>
42
43
(-)a/koha-tmpl/intranet-tmpl/prog/js/ill-availability-partner.js (+24 lines)
Line 0 Link Here
1
$(document).ready(function() {
2
    $('#partners').change(function() {
3
        var selected = [];
4
        $('#partners option:selected').each(function() {
5
            selected.push($(this).data('partner-id'));
6
        });
7
        if (selected.length > 0) {
8
            $('#generic_confirm_search').css('visibility', 'initial');
9
        } else {
10
            $('#generic_confirm_search').css('visibility', 'hidden');
11
        }
12
        $('#service_id_restrict').
13
            attr('data-service_id_restrict_ids', selected.join('|'));
14
    });
15
    $('#generic_confirm_search').click(function(e) {
16
        $('#partnerSearch').modal({show:true});
17
    });
18
    $('#partnerSearch').on('show.bs.modal', function() {
19
        doSearch();
20
    });
21
    $('#partnerSearch').on('hide.bs.modal', function() {
22
        $.fn.dataTable.tables({ api: true }).destroy();
23
    });
24
});
(-)a/koha-tmpl/intranet-tmpl/prog/js/ill-availability.js (+222 lines)
Line 0 Link Here
1
$(document).ready(function() {
2
3
    window.doSearch = function() {
4
        // In case the source doesn't supply data required for DT to calculate
5
        // pagination, we need to do it ourselves
6
        var ownPagination = false;
7
        var directionSet = false;
8
        var start = 0;
9
        var forward = true; // true == forward, false == backwards
10
        // Arbitrary starting value, it will be corrected by the first
11
        // page of results
12
        var pageSize = 20;
13
14
        var tableTmpl = {
15
            ajax: {
16
                cache: true, // Prevent DT appending a "_" cache param
17
            },
18
            columns: [
19
                // defaultContent prevents DT from choking if
20
                // the API response doesn't return a column
21
                {
22
                    title: 'Source',
23
                    data: 'source',
24
                    defaultContent: ''
25
                },
26
                {
27
                    data: 'title',
28
                    defaultContent: ''
29
                },
30
                {
31
                    data: 'author',
32
                    defaultContent: ''
33
                },
34
                {
35
                    data: 'isbn',
36
                    defaultContent: ''
37
                },
38
                {
39
                    data: 'issn',
40
                    defaultContent: ''
41
                },
42
                {
43
                    data: 'date',
44
                    defaultContent: ''
45
                }
46
            ]
47
        };
48
49
        // render functions don't get copied across when we make a dereferenced
50
        // copy of them, so we have to reattach them once we have a copy
51
        // Here we store them
52
        var renders = {
53
            title: function(data, type, row) {
54
                return row.url ?
55
                    '<a href="'+row.url+'" target="_blank">'+row.title+'</a>' :
56
                    row.title;
57
            },
58
            source: function(data, type, row) {
59
                return row.opac_url ?
60
                    '<a href="'+row.opac_url+'" target="_blank">'+row.source+'</a>' :
61
                    row.source;
62
            }
63
        };
64
65
        services.forEach(function(service) {
66
            // Create a deferenced copy of our table definition object
67
            var tableDef = JSON.parse(JSON.stringify(tableTmpl));
68
            // Iterate the table's columns array and add render functions
69
            // as necessary
70
            tableDef.columns.forEach(function(column) {
71
                if (renders[column.data]) {
72
                    column.render = renders[column.data];
73
                }
74
            });
75
            tableDef.ajax.dataSrc = function(data) {
76
                var results = data.results.search_results;
77
                // The source appears to be returning it's own pagination
78
                // data
79
                if (
80
                    data.hasOwnProperty('recordsFiltered') ||
81
                    data.hasOwnProperty('recordsTotal')
82
                ) {
83
                    return results;
84
                }
85
                // Set up our own pagination values based on what we just
86
                // got back
87
                ownPagination = true;
88
                directionSet = false;
89
                pageSize = results.length;
90
                // These values are completely arbitrary, but they enable
91
                // us to display pagination links
92
                data.recordsFiltered = 5000,
93
                data.recordsTotal = 5000;
94
95
                return results;
96
            };
97
            tableDef.ajax.data = function(data) {
98
                // Datatables sends a bunch of superfluous params
99
                // that we don't want to litter our API schema
100
                // with, so just remove them from the request
101
                if (data.hasOwnProperty('columns')) {
102
                    delete data.columns;
103
                }
104
                if (data.hasOwnProperty('draw')) {
105
                    delete data.draw;
106
                }
107
                if (data.hasOwnProperty('order')) {
108
                    delete data.order;
109
                }
110
                if (data.hasOwnProperty('search')) {
111
                    delete data.search;
112
                }
113
                // If we're handling our own pagination, set the properties
114
                // that DT will send in the request
115
                if (ownPagination) {
116
                    start = forward ? start + pageSize : start - pageSize;
117
                    data.start = start;
118
                    data['length'] = pageSize;
119
                }
120
                // We may need to restrict the service IDs being queries, this
121
                // needs to be handled in the plugin's API module
122
                var restrict = $('#service_id_restrict').
123
                    attr('data-service_id_restrict_ids');
124
                if (restrict && restrict.length > 0) {
125
                    data.restrict = restrict;
126
                }
127
            };
128
            // Add any datatables config options passed from the service
129
            // to the table definition
130
            tableDef.ajax.url = service.endpoint + metadata;
131
            if (service.hasOwnProperty('datatablesConfig')) {
132
                var conf = service.datatablesConfig;
133
                for (var key in conf) {
134
                    // The config from the service definition comes from a Perl
135
                    // hashref, therefore can't contain true/false, so we
136
                    // special case it
137
                    if (conf.hasOwnProperty(key)) {
138
                        if (conf[key] == 'false') {
139
                            // Special case false values
140
                            tableDef[key] = false;
141
                        } else if (conf[key] == 'true') {
142
                            // Special case true values
143
                            tableDef[key] = true;
144
                        } else {
145
                            // Copy the property value
146
                            tableDef[key] = conf[key];
147
                        }
148
                    }
149
                }
150
            }
151
            // Create event watchers for the "next" and "previous" pagination
152
            // links, this enables us to set the direction the next request is
153
            // going in when we're doing our own pagination. We use "hover"
154
            // because the click event is caught after the request has been
155
            // sent
156
            tableDef.drawCallback = function() {
157
                $('.paginate_button.next:not(.disabled)',
158
                    this.api().table().container()
159
                ).on('hover', function() {
160
                    forward = true;
161
                    directionSet = true;
162
                });
163
                $('.paginate_button.previous:not(.disabled)',
164
                    this.api().table().container()
165
                ).on('hover', function() {
166
                    forward = false;
167
                    directionSet = true;
168
                });
169
            }
170
            // Initialise the table
171
            // Since we're not able to use the columns settings in core,
172
            // we need to mock the object that it would return
173
            var columns_settings = [
174
				{
175
					cannot_be_modified: 0,
176
					cannot_be_toggled: 0,
177
					columnname: 'source',
178
					is_hidden: 0
179
				},
180
				{
181
					cannot_be_modified: 0,
182
					cannot_be_toggled: 0,
183
					columnname: 'title',
184
					is_hidden: 0
185
				},
186
				{
187
					cannot_be_modified: 0,
188
					cannot_be_toggled: 0,
189
					columnname: 'author',
190
					is_hidden: 0
191
				},
192
				{
193
					cannot_be_modified: 0,
194
					cannot_be_toggled: 0,
195
					columnname: 'isbn',
196
					is_hidden: 0
197
				},
198
				{
199
					cannot_be_modified: 0,
200
					cannot_be_toggled: 0,
201
					columnname: 'issn',
202
					is_hidden: 0
203
				},
204
				{
205
					cannot_be_modified: 0,
206
					cannot_be_toggled: 0,
207
					columnname: 'date',
208
					is_hidden: 0
209
				}
210
            ];
211
            // Hide pagination buttons if appropriate
212
            tableDef.drawCallback = function() {
213
                var pagination = $(this).closest('.dataTables_wrapper')
214
                    .find('.dataTables_paginate');
215
                pagination.toggle(this.api().page.info().pages > 1);
216
            }
217
            KohaTable(service.id, tableDef, columns_settings);
218
        });
219
    }
220
221
222
});
(-)a/koha-tmpl/opac-tmpl/bootstrap/css/src/opac.scss (+9 lines)
Lines 2919-2924 button { Link Here
2919
    .dropdown:hover .dropdown-menu.nojs {
2919
    .dropdown:hover .dropdown-menu.nojs {
2920
        display: block;
2920
        display: block;
2921
    }
2921
    }
2922
2923
}
2924
2925
.ill_availability_sourcename {
2926
    margin-top: 20px;
2927
}
2928
2929
#continue-request-row {
2930
    text-align: center;
2922
}
2931
}
2923
2932
2924
#dc_fieldset {
2933
#dc_fieldset {
(-)a/koha-tmpl/opac-tmpl/bootstrap/en/includes/ill-availability-table.inc (+17 lines)
Line 0 Link Here
1
<div>
2
    <div>[% service.name %]</div>
3
    <table class="ill-availability table table-bordered table-striped" id="[% service.id %]">
4
        <thead id="[% service.id %]-header">
5
            <tr>
6
                <th>Source</th>
7
                <th>Title</th>
8
                <th>Author</th>
9
                <th>ISBN</th>
10
                <th>ISSN</th>
11
                <th>Date</th>
12
            </tr>
13
        </thead>
14
        <tbody id="[% service.id %]-body">
15
        </tbody>
16
    </table>
17
</div>
(-)a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-illrequests.tt (+47 lines)
Lines 224-229 Link Here
224
                                <span class="cancel"><a href="/cgi-bin/koha/opac-illrequests.pl">Cancel</a></span>
224
                                <span class="cancel"><a href="/cgi-bin/koha/opac-illrequests.pl">Cancel</a></span>
225
                            </fieldset>
225
                            </fieldset>
226
                        </form>
226
                        </form>
227
                    [% ELSIF method == 'availability' %]
228
                        <h2>Interlibrary loan item availability</h2>
229
                        <div id="results">
230
                            <h3>Displaying availability results</h3>
231
                            <form method="POST" action="/cgi-bin/koha/opac-illrequests.pl">
232
                                [% FOREACH key IN whole.keys %]
233
                                    [% value = whole.$key %]
234
                                    [% IF key != 'custom_key' && key != 'custom_value' %]
235
                                    <input type="hidden" name="[% key | html %]" value="[% value | html %]">
236
                                    [% END %]
237
                                [% END %]
238
                                [% custom_keys = whole.custom_key.split('\0') %]
239
                                [% custom_values = whole.custom_value.split('\0') %]
240
                                [% i = 0 %]
241
                                [% FOREACH custom_key IN custom_keys %]
242
                                    <input type="hidden" name="custom_key" value="[% custom_key %]">
243
                                    <input type="hidden" name="custom_value" value="[% custom_values.$i %]">
244
                                [% i = i + 1 %]
245
                                [% END %]
246
                                <input type="hidden" name="checked_availability" value="1">
247
                                <div id="continue-request-row" class="alert">
248
                                    If you can't find what you are looking for, you can
249
                                    <button class="button" type="submit">continue creating your request</button> or
250
                                    <a href="/cgi-bin/koha/opac-illrequests.pl">cancel your request</a>
251
                                </div>
252
                            </form>
253
                            [% FOR service IN services %]
254
                                <h4 class="ill_availability_sourcename">[% service.plugin %]</h4>
255
                                [% INCLUDE 'ill-availability-table.inc' service=service %]
256
                            [% END %]
257
                        </div>
227
                    [% END %]
258
                    [% END %]
228
                </div> <!-- / .maincontent -->
259
                </div> <!-- / .maincontent -->
229
          [% END %]
260
          [% END %]
Lines 247-254 Link Here
247
            "deferRender": true
278
            "deferRender": true
248
        }));
279
        }));
249
        $("#backend-dropdown-options").removeClass("nojs");
280
        $("#backend-dropdown-options").removeClass("nojs");
281
        [% IF services_json.length > 0 %]
282
        var services = [% services_json | $raw %];
283
        [% ELSE %]
284
        var services = [];
285
        [% END %]
286
        [% IF metadata.length > 0 %]
287
        var metadata = "[% metadata | $raw %]";
288
        [% END %]
250
    //]]>
289
    //]]>
251
</script>
290
</script>
291
[% IF method == 'availability' %]
292
    [% Asset.js("js/ill-availability.js") | $raw %]
293
    <script>
294
        $(document).ready(function() {
295
            window.doSearch();
296
        })
297
    </script>
298
[% END %]
252
[% TRY %]
299
[% TRY %]
253
[% PROCESS backend_jsinclude %]
300
[% PROCESS backend_jsinclude %]
254
[% CATCH %]
301
[% CATCH %]
(-)a/koha-tmpl/opac-tmpl/bootstrap/js/ill-availability.js (+178 lines)
Line 0 Link Here
1
$(document).ready(function() {
2
3
    window.doSearch = function() {
4
        // In case the source doesn't supply data required for DT to calculate
5
        // pagination, we need to do it ourselves
6
        var ownPagination = false;
7
        var directionSet = false;
8
        var start = 0;
9
        var forward = true; // true == forward, false == backwards
10
        // Arbitrary starting value, it will be corrected by the first
11
        // page of results
12
        var pageSize = 20;
13
14
        var tableTmpl = {
15
            ajax: {
16
                cache: true, // Prevent DT appending a "_" cache param
17
            },
18
            columns: [
19
                // defaultContent prevents DT from choking if
20
                // the API response doesn't return a column
21
                {
22
                    title: 'Source',
23
                    data: 'source',
24
                    defaultContent: ''
25
                },
26
                {
27
                    data: 'title',
28
                    defaultContent: ''
29
                },
30
                {
31
                    data: 'author',
32
                    defaultContent: ''
33
                },
34
                {
35
                    data: 'isbn',
36
                    defaultContent: ''
37
                },
38
                {
39
                    data: 'issn',
40
                    defaultContent: ''
41
                },
42
                {
43
                    data: 'date',
44
                    defaultContent: ''
45
                }
46
            ]
47
        };
48
49
        // render functions don't get copied across when we make a dereferenced
50
        // copy of them, so we have to reattach them once we have a copy
51
        // Here we store them
52
        var renders = {
53
            title: function(data, type, row) {
54
                return row.url ?
55
                    '<a href="'+row.url+'" target="_blank">'+row.title+'</a>' :
56
                    row.title;
57
            },
58
            source: function(data, type, row) {
59
                return row.opac_url ?
60
                    '<a href="'+row.opac_url+'" target="_blank">'+row.source+'</a>' :
61
                    row.source;
62
            }
63
        };
64
65
        services.forEach(function(service) {
66
            // Create a deferenced copy of our table definition object
67
            var tableDef = JSON.parse(JSON.stringify(tableTmpl));
68
            // Iterate the table's columns array and add render functions
69
            // as necessary
70
            tableDef.columns.forEach(function(column) {
71
                if (renders[column.data]) {
72
                    column.render = renders[column.data];
73
                }
74
            });
75
            tableDef.ajax.dataSrc = function(data) {
76
                var results = data.results.search_results;
77
                // The source appears to be returning it's own pagination
78
                // data
79
                if (
80
                    data.hasOwnProperty('recordsFiltered') ||
81
                    data.hasOwnProperty('recordsTotal')
82
                ) {
83
                    return results;
84
                }
85
                // Set up our own pagination values based on what we just
86
                // got back
87
                ownPagination = true;
88
                directionSet = false;
89
                pageSize = results.length;
90
                // These values are completely arbitrary, but they enable
91
                // us to display pagination links
92
                data.recordsFiltered = 5000,
93
                data.recordsTotal = 5000;
94
95
                return results;
96
            };
97
            tableDef.ajax.data = function(data) {
98
                // Datatables sends a bunch of superfluous params
99
                // that we don't want to litter our API schema
100
                // with, so just remove them from the request
101
                if (data.hasOwnProperty('columns')) {
102
                    delete data.columns;
103
                }
104
                if (data.hasOwnProperty('draw')) {
105
                    delete data.draw;
106
                }
107
                if (data.hasOwnProperty('order')) {
108
                    delete data.order;
109
                }
110
                if (data.hasOwnProperty('search')) {
111
                    delete data.search;
112
                }
113
                // If we're handling our own pagination, set the properties
114
                // that DT will send in the request
115
                if (ownPagination) {
116
                    start = forward ? start + pageSize : start - pageSize;
117
                    data.start = start;
118
                    data['length'] = pageSize;
119
                }
120
                // We may need to restrict the service IDs being queries, this
121
                // needs to be handled in the plugin's API module
122
                var restrict = $('#service_id_restrict').
123
                    attr('data-service_id_restrict_ids');
124
                if (restrict && restrict.length > 0) {
125
                    data.restrict = restrict;
126
                }
127
            };
128
            // Add any datatables config options passed from the service
129
            // to the table definition
130
            tableDef.ajax.url = service.endpoint + metadata;
131
            if (service.hasOwnProperty('datatablesConfig')) {
132
                var conf = service.datatablesConfig;
133
                for (var key in conf) {
134
                    // The config from the service definition comes from a Perl
135
                    // hashref, therefore can't contain true/false, so we
136
                    // special case it
137
                    if (conf.hasOwnProperty(key)) {
138
                        if (conf[key] == 'false') {
139
                            // Special case false values
140
                            tableDef[key] = false;
141
                        } else if (conf[key] == 'true') {
142
                            // Special case true values
143
                            tableDef[key] = true;
144
                        } else {
145
                            // Copy the property value
146
                            tableDef[key] = conf[key];
147
                        }
148
                    }
149
                }
150
            }
151
            // Create event watchers for the "next" and "previous" pagination
152
            // links, this enables us to set the direction the next request is
153
            // going in when we're doing our own pagination. We use "hover"
154
            // because the click event is caught after the request has been
155
            // sent
156
            tableDef.drawCallback = function() {
157
                $('.paginate_button.next:not(.disabled)',
158
                    this.api().table().container()
159
                ).on('hover', function() {
160
                    forward = true;
161
                    directionSet = true;
162
                });
163
                $('.paginate_button.previous:not(.disabled)',
164
                    this.api().table().container()
165
                ).on('hover', function() {
166
                    forward = false;
167
                    directionSet = true;
168
                });
169
            }
170
            // Initialise the table
171
            $('#'+service.id ).dataTable(
172
                $.extend(true, {}, dataTablesDefaults, tableDef)
173
            );
174
        });
175
    }
176
177
178
});
(-)a/opac/opac-illrequests.pl (-1 / +43 lines)
Lines 19-24 Link Here
19
19
20
use Modern::Perl;
20
use Modern::Perl;
21
21
22
use JSON qw( encode_json );
23
22
use CGI qw ( -utf8 );
24
use CGI qw ( -utf8 );
23
use C4::Auth;
25
use C4::Auth;
24
use C4::Koha;
26
use C4::Koha;
Lines 28-33 use Koha::Illrequest::Config; Link Here
28
use Koha::Illrequests;
30
use Koha::Illrequests;
29
use Koha::Libraries;
31
use Koha::Libraries;
30
use Koha::Patrons;
32
use Koha::Patrons;
33
use Koha::Illrequest::Availability;
31
34
32
my $query = new CGI;
35
my $query = new CGI;
33
36
Lines 110-115 if ( $op eq 'list' ) { Link Here
110
    } else {
113
    } else {
111
        my $request = Koha::Illrequest->new
114
        my $request = Koha::Illrequest->new
112
            ->load_backend($params->{backend});
115
            ->load_backend($params->{backend});
116
117
        # Does this backend enable us to insert an availability stage and should
118
        # we? If not, proceed as normal.
119
        if (
120
            C4::Context->preference("ILLCheckAvailability") &&
121
            $request->_backend_capability(
122
                'should_display_availability',
123
                $params
124
            ) &&
125
            # If the user has elected to continue with the request despite
126
            # having viewed availability info, this flag will be set
127
            !$params->{checked_availability}
128
        ) {
129
            # Establish which of the installed availability providers
130
            # can service our metadata, if so, jump in
131
            my $availability = Koha::Illrequest::Availability->new($params);
132
            my $services = $availability->get_services({
133
                ui_context => 'opac'
134
            });
135
            if (scalar @{$services} > 0) {
136
                # Modify our method so we use the correct part of the
137
                # template
138
                $op = 'availability';
139
                # Prepare the metadata we're sending them
140
                my $metadata = $availability->prep_metadata($params);
141
                $template->param(
142
                    metadata        => $metadata,
143
                    services_json   => encode_json($services),
144
                    services        => $services,
145
                    illrequestsview => 1,
146
                    message         => $params->{message},
147
                    method          => $op,
148
                    whole           => $params
149
                );
150
                output_html_with_http_headers $query, $cookie,
151
                    $template->output, undef, { force_no_caching => 1 };
152
                exit;
153
            }
154
        }
155
113
        $params->{cardnumber} = Koha::Patrons->find({
156
        $params->{cardnumber} = Koha::Patrons->find({
114
            borrowernumber => $loggedinuser
157
            borrowernumber => $loggedinuser
115
        })->cardnumber;
158
        })->cardnumber;
116
- 

Return to bug 23173